diff --git a/.github/workflows/java-21-builds.yml b/.github/workflows/java-21-builds.yml index 631b25d70c1..dfd4ffc9456 100644 --- a/.github/workflows/java-21-builds.yml +++ b/.github/workflows/java-21-builds.yml @@ -13,9 +13,9 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" uses: ./.github/workflows/parallelize-tests.yml with: - environments: 1.21.4,1.21.5,1.21.8,1.21.10,1.21.11 + environments: 1.21.5,1.21.8,1.21.10,1.21.11 java_version: 21 - parallel_jobs: 3 + parallel_jobs: 2 build: name: ${{ matrix.display }} diff --git a/.github/workflows/junit-21-builds.yml b/.github/workflows/junit-21-builds.yml index 71dcde9b041..4050da5d768 100644 --- a/.github/workflows/junit-21-builds.yml +++ b/.github/workflows/junit-21-builds.yml @@ -13,9 +13,9 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" uses: ./.github/workflows/parallelize-tests.yml with: - environments: 1.21.4,1.21.5,1.21.8,1.21.10,1.21.11 + environments: 1.21.5,1.21.8,1.21.10,1.21.11 java_version: 21 - parallel_jobs: 3 + parallel_jobs: 2 build: name: ${{ matrix.display }} diff --git a/build.gradle b/build.gradle index ae017481091..b8f6c009b73 100644 --- a/build.gradle +++ b/build.gradle @@ -295,7 +295,7 @@ tasks.register('JUnit') { // custom test task // usage: gradle customTest -PtestEnvs= -PtestEnvJavaVersion= -Ptimeout= -Pjunit=[true|false] // defaults: testEnvJavaVersion=latestJava, timeout=0, junit=false -// example: gradle customTest -PtestEnvs="java21/paper-1.21.4,java21/paper-1.21.8" -PtestEnvJavaVersion=21 -Ptimeout=600000 -Pjunit=true +// example: gradle customTest -PtestEnvs="java21/paper-1.21.5,java21/paper-1.21.8" -PtestEnvJavaVersion=21 -Ptimeout=600000 -Pjunit=true // get environments String propEnvs = project.hasProperty('testEnvs') ? project.property('testEnvs') as String : project.testEnv diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 216bc6687d8..441cdb9da47 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -20,6 +20,7 @@ import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.registrations.Feature; +import ch.njol.skript.structures.StructFunction; import ch.njol.skript.test.runner.*; import ch.njol.skript.timings.SkriptTimings; import ch.njol.skript.update.ReleaseManifest; @@ -68,6 +69,7 @@ import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; import org.skriptlang.skript.bukkit.text.TextComponentParser; import org.skriptlang.skript.common.CommonModule; +import org.skriptlang.skript.common.function.FunctionRegistry; import org.skriptlang.skript.docs.Origin; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; @@ -480,6 +482,11 @@ public void onEnable() { //noinspection removal EventValues.setEventValueRegistry(eventValueRegistry); + FunctionRegistry functionRegistry = FunctionRegistry.empty(skript); + skript.storeRegistry(FunctionRegistry.class, functionRegistry); + ch.njol.skript.lang.function.FunctionRegistry.setNewRegistry(functionRegistry); + StructFunction.setRegistry(functionRegistry); + // TODO this upcoming portion is a bad circular dependency // some modules depend on the config // the config depends on some modules (for the types they register) diff --git a/src/main/java/ch/njol/skript/bukkitutil/BukkitUtils.java b/src/main/java/ch/njol/skript/bukkitutil/BukkitUtils.java index 0b336b01b3a..cf73abf6bdd 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/BukkitUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/BukkitUtils.java @@ -7,6 +7,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.Keyed; import org.bukkit.Registry; import org.bukkit.inventory.EquipmentSlot; @@ -89,8 +90,25 @@ public static EquipmentSlot getEquipmentSlotFromIndex(int slotIndex) { String codeName, String languageNode ) { + // validate class if (!Skript.classExists(classPath)) return null; + Class registryClass; + try { + //noinspection unchecked + registryClass = (Class) Class.forName(classPath); + } catch (ClassNotFoundException e) { + Skript.debug("Could not retrieve the class with the path: '" + classPath + "'."); + throw new RuntimeException(e); + } + + // first, try better RegistryKey + if (PaperUtils.registryExists(registryName)) { + RegistryKey registryKey = PaperUtils.getBukkitRegistryKey(registryName); + return new RegistryClassInfo<>(registryClass, registryKey, codeName, languageNode); + } + + // otherwise, standard Registry Registry registry = null; if (BukkitUtils.registryExists(registryName)) { try { @@ -99,18 +117,8 @@ public static EquipmentSlot getEquipmentSlotFromIndex(int slotIndex) { } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } - } else if (PaperUtils.registryExists(registryName)) { - registry = PaperUtils.getBukkitRegistry(registryName); } if (registry != null) { - Class registryClass; - try { - //noinspection unchecked - registryClass = (Class) Class.forName(classPath); - } catch (ClassNotFoundException e) { - Skript.debug("Could not retrieve the class with the path: '" + classPath + "'."); - throw new RuntimeException(e); - } return new RegistryClassInfo<>(registryClass, registry, codeName, languageNode); } Skript.debug("There were no registries found for '" + registryName + "'."); diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index 2852a33ac26..ee47a37fea8 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -133,7 +133,6 @@ public ClassInfo user(final String... userInputPatterns) throws PatternSyntax * @see SimpleLiteral */ public ClassInfo defaultExpression(final DefaultExpression defaultExpression) { - assert this.defaultExpression == null; if (!defaultExpression.isDefault()) throw new IllegalArgumentException("defaultExpression.isDefault() must return true for the default expression of a class"); this.defaultExpression = defaultExpression; diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 3ad45cd73b2..7089b80c64a 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -11,6 +11,7 @@ import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.BlockUtils; import ch.njol.yggdrasil.Fields; +import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.world.MoonPhase; import net.kyori.adventure.text.Component; import org.bukkit.*; @@ -326,7 +327,7 @@ public String toVariableNameString(InventoryHolder holder) { .since("2.0") .changer(DefaultChangers.itemChanger)); - Classes.registerClass(new RegistryClassInfo<>(Biome.class, Registry.BIOME, "biome", "biomes") + Classes.registerClass(new RegistryClassInfo<>(Biome.class, RegistryKey.BIOME, "biome", "biomes") .user("biomes?") .name("Biome") .description("All possible biomes Minecraft uses to generate a world.", @@ -600,7 +601,7 @@ public String[] getPatterns() { ExpressionPropertyHandler.of(GameRule::getName, String.class) )); - Classes.registerClass(new RegistryClassInfo<>(Attribute.class, Registry.ATTRIBUTE, "attributetype", "attribute types") + Classes.registerClass(new RegistryClassInfo<>(Attribute.class, RegistryKey.ATTRIBUTE, "attributetype", "attribute types") .user("attribute ?types?") .name("Attribute Type") .description("Represents the type of an attribute. Note that this type does not contain any numerical values." + @@ -659,14 +660,14 @@ public String[] getPatterns() { .description("Represents a change reason of an experience cooldown change event.") .since("2.10")); - Classes.registerClass(new RegistryClassInfo<>(Villager.Type.class, Registry.VILLAGER_TYPE, "villagertype", "villager types") + Classes.registerClass(new RegistryClassInfo<>(Villager.Type.class, RegistryKey.VILLAGER_TYPE, "villagertype", "villager types") .user("villager ?types?") .name("Villager Type") .description("Represents the different types of villagers. These are usually the biomes a villager can be from.") .after("biome") .since("2.10")); - Classes.registerClass(new RegistryClassInfo<>(Villager.Profession.class, Registry.VILLAGER_PROFESSION, "villagerprofession", "villager professions") + Classes.registerClass(new RegistryClassInfo<>(Villager.Profession.class, RegistryKey.VILLAGER_PROFESSION, "villagerprofession", "villager professions") .user("villager ?professions?") .name("Villager Profession") .description("Represents the different professions of villagers.") @@ -705,27 +706,7 @@ public String toVariableNameString(EntitySnapshot snapshot) { .description("Represents a banner pattern.") .since("2.10")); - ClassInfo patternTypeInfo; - Registry patternRegistry = Bukkit.getRegistry(PatternType.class); - if (patternRegistry != null) { - patternTypeInfo = new RegistryClassInfo<>(PatternType.class, patternRegistry, "bannerpatterntype", "banner pattern types"); - } else { - try { - Class patternClass = Class.forName("org.bukkit.block.banner.PatternType"); - if (patternClass.isEnum()) { - //noinspection unchecked,rawtypes - Class enumClass = (Class) patternClass; - //noinspection rawtypes,unchecked - patternTypeInfo = new EnumClassInfo<>(enumClass, "bannerpatterntype", "banner pattern types"); - } else { - throw new IllegalStateException("PatternType is neither an enum nor a valid registry."); - } - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - Classes.registerClass(patternTypeInfo + Classes.registerClass(new RegistryClassInfo<>(PatternType.class, RegistryKey.BANNER_PATTERN, "bannerpatterntype", "banner pattern types") .user("banner ?pattern ?types?") .name("Banner Pattern Type") .description("Represents the various banner patterns that can be applied to a banner.") diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index 818b402af0c..981f44fda0c 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -365,7 +365,7 @@ Turns a number in a string using a specific base (decimal, hexadecimal, octal). ) .since("2.14") .parameter("n", Long[].class) - .parameter("base", Long.class, Modifier.ranged(2, 36)) + .parameter("base", Long.class, new Modifier.Ranged<>(2, 36)) .contract(new Contract() { @Override public boolean isSingle(Expression... arguments) { @@ -405,7 +405,7 @@ Turns a text version of a number in a specific base (decimal, hexadecimal, octal """) .since("2.14") .parameter("string value", String[].class) - .parameter("base", Long.class, Modifier.ranged(2, 36)) + .parameter("base", Long.class, new Modifier.Ranged<>(2, 36)) .contract(new Contract() { @Override public boolean isSingle(Expression... arguments) { @@ -482,9 +482,9 @@ set block at location({_x}, {_y}, {_z}, {_w}) to stone .parameter("x", Number.class) .parameter("y", Number.class) .parameter("z", Number.class) - .parameter("world", World.class, Modifier.OPTIONAL) - .parameter("yaw", Float.class, Modifier.OPTIONAL) - .parameter("pitch", Float.class, Modifier.OPTIONAL) + .parameter("world", World.class, new Modifier.Optional()) + .parameter("yaw", Float.class, new Modifier.Optional()) + .parameter("pitch", Float.class, new Modifier.Optional()) .build(args -> { World world = args.getOrDefault("world", Bukkit.getWorlds().get(0)); @@ -612,10 +612,10 @@ public Long[] executeSimple(Object[][] params) { "set the colour of a text display to rgb(10, 50, 100, 50)" ) .since("2.5, 2.10 (alpha)") - .parameter("red", Long.class, Modifier.ranged(0, 255)) - .parameter("green", Long.class, Modifier.ranged(0, 255)) - .parameter("blue", Long.class, Modifier.ranged(0, 255)) - .parameter("alpha", Long.class, Modifier.ranged(0, 255), Modifier.OPTIONAL) + .parameter("red", Long.class, new Modifier.Ranged<>(0, 255)) + .parameter("green", Long.class, new Modifier.Ranged<>(0, 255)) + .parameter("blue", Long.class, new Modifier.Ranged<>(0, 255)) + .parameter("alpha", Long.class, new Modifier.Ranged<>(0, 255), new Modifier.Optional()) .build(args -> ColorRGB.fromRGBA( args.get("red").intValue(), args.get("green").intValue(), @@ -635,7 +635,7 @@ public Long[] executeSimple(Object[][] params) { ) .since("2.8.0") .parameter("nameOrUUID", String.class) - .parameter("getExactPlayer", Boolean.class, Modifier.OPTIONAL) + .parameter("getExactPlayer", Boolean.class, new Modifier.Optional()) .build(args -> { String name = args.get("nameOrUUID"); boolean isExact = args.getOrDefault("getExactPlayer", false); diff --git a/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java index ee197f03600..c93bfb5856d 100644 --- a/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/registry/RegistryClassInfo.java @@ -3,10 +3,16 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.DefaultExpression; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.Keyed; import org.bukkit.Registry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Iterator; import java.util.function.Consumer; +import java.util.function.Supplier; /** * This class can be used for easily creating ClassInfos for {@link Registry}s. @@ -16,12 +22,87 @@ */ public class RegistryClassInfo extends ClassInfo { + private final @Nullable RegistryKey registryKey; + + /** + * @param registryClass The registry class + * @param registryKey The registry key + * @param codeName The name used in patterns + * @param languageNode The language node of the type + */ + public RegistryClassInfo(Class registryClass, RegistryKey registryKey, String codeName, String languageNode) { + this(registryClass, registryKey, RegistryAccess.registryAccess().getRegistry(registryKey), codeName, + languageNode, null, null); + } + + /** + * @param registryClass The registry class + * @param registryKey The registry key + * @param codeName The name used in patterns + * @param languageNode The language node of the type + * @param parseCallback A consumer to run on a successful parse. + */ + public RegistryClassInfo(Class registryClass, RegistryKey registryKey, String codeName, String languageNode, + Consumer parseCallback) { + this(registryClass, registryKey, RegistryAccess.registryAccess().getRegistry(registryKey), codeName, + languageNode, null, parseCallback); + } + + private RegistryClassInfo(Class registryClass, @Nullable RegistryKey registryKey, Registry registry, + String codeName, String languageNode, @Nullable DefaultExpression defaultExpression, + @Nullable Consumer parseCallback) { + super(registryClass, codeName); + + if (defaultExpression == null) { + defaultExpression = new EventValueExpression<>(registryClass); + } + if (parseCallback == null) { + parseCallback = ignored -> { }; + } + + this.registryKey = registryKey; + RegistryParser registryParser = new RegistryParser<>(registry, languageNode, parseCallback); + usage(registryParser.getCombinedPatterns()) + .supplier(registry::iterator) + .serializer(new RegistrySerializer<>(registry)) + .defaultExpression(defaultExpression) + .parser(registryParser); + } + + public @Nullable RegistryKey registryKey() { + return registryKey; + } + + @Override + public @NotNull RegistryParser getParser() { + //noinspection ConstantConditions, unchecked + return (RegistryParser) super.getParser(); + } + + @Override + public @NotNull RegistrySerializer getSerializer() { + //noinspection ConstantConditions, unchecked + return (RegistrySerializer) super.getSerializer(); + } + + @Override + public @NotNull Supplier> getSupplier() { + //noinspection ConstantConditions + return super.getSupplier(); + } + + /* + * Legacy Constructors + */ + /** * @param registryClass The registry class * @param registry The registry * @param codeName The name used in patterns * @param languageNode The language node of the type + * @deprecated Use {@link #RegistryClassInfo(Class, RegistryKey, String, String)}. */ + @Deprecated(since = "INSERT VERSION", forRemoval = true) public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode) { this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass)); } @@ -32,7 +113,9 @@ public RegistryClassInfo(Class registryClass, Registry registry, String co * @param codeName The name used in patterns * @param languageNode The language node of the type * @param parseCallback A consumer to run on a successful parse. + * @deprecated Use {@link #RegistryClassInfo(Class, RegistryKey, String, String, Consumer)}. */ + @Deprecated(since = "INSERT VERSION", forRemoval = true) public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, Consumer parseCallback) { this(registryClass, registry, codeName, languageNode, new EventValueExpression<>(registryClass), parseCallback); } @@ -43,8 +126,11 @@ public RegistryClassInfo(Class registryClass, Registry registry, String co * @param codeName The name used in patterns * @param languageNode The language node of the type * @param defaultExpression The default expression of the type + * @deprecated Use {@link #RegistryClassInfo(Class, RegistryKey, String, String)} with {@link #defaultExpression(DefaultExpression)}. */ - public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression) { + @Deprecated(since = "INSERT VERSION", forRemoval = true) + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, + DefaultExpression defaultExpression) { this(registryClass, registry, codeName, languageNode, defaultExpression, ignored -> {}); } @@ -55,15 +141,13 @@ public RegistryClassInfo(Class registryClass, Registry registry, String co * @param languageNode The language node of the type * @param defaultExpression The default expression of the type * @param parseCallback A consumer to run on a successful parse. + * @deprecated Use {@link #RegistryClassInfo(Class, RegistryKey, String, String, Consumer)} + * with {@link #defaultExpression(DefaultExpression)}. */ - public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression, Consumer parseCallback) { - super(registryClass, codeName); - RegistryParser registryParser = new RegistryParser<>(registry, languageNode, parseCallback); - usage(registryParser.getCombinedPatterns()) - .supplier(registry::iterator) - .serializer(new RegistrySerializer<>(registry)) - .defaultExpression(defaultExpression) - .parser(registryParser); + @Deprecated(since = "INSERT VERSION", forRemoval = true) + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, + DefaultExpression defaultExpression, Consumer parseCallback) { + this(registryClass, null, registry, codeName, languageNode, defaultExpression, parseCallback); } /** @@ -89,7 +173,8 @@ public RegistryClassInfo(Class registryClass, Registry registry, String co * @deprecated {@code registerComparator} is no longer necessary. */ @Deprecated(since = "2.16", forRemoval = true) - public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, DefaultExpression defaultExpression, boolean registerComparator) { + public RegistryClassInfo(Class registryClass, Registry registry, String codeName, String languageNode, + DefaultExpression defaultExpression, boolean registerComparator) { this(registryClass, registry, codeName, languageNode, defaultExpression); } diff --git a/src/main/java/ch/njol/skript/entity/CatData.java b/src/main/java/ch/njol/skript/entity/CatData.java index a49ad39205f..e387bce9d0c 100644 --- a/src/main/java/ch/njol/skript/entity/CatData.java +++ b/src/main/java/ch/njol/skript/entity/CatData.java @@ -1,15 +1,12 @@ package ch.njol.skript.entity; -import ch.njol.skript.bukkitutil.BukkitUtils; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.EnumClassInfo; import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; import ch.njol.util.coll.CollectionUtils; import com.google.common.collect.Iterators; -import org.bukkit.Registry; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.entity.Cat; import org.bukkit.entity.Cat.Type; import org.jetbrains.annotations.NotNull; @@ -22,13 +19,7 @@ public class CatData extends EntityData { private static final Type[] TYPES; static { - ClassInfo catTypeClassInfo; - if (BukkitUtils.registryExists("CAT_VARIANT")) { - catTypeClassInfo = new RegistryClassInfo<>(Cat.Type.class, Registry.CAT_VARIANT, "cattype", "cat types"); - } else { - //noinspection unchecked, rawtypes - it is an enum on other versions - catTypeClassInfo = new EnumClassInfo<>((Class) Cat.Type.class, "cattype", "cat types"); - } + var catTypeClassInfo = new RegistryClassInfo<>(Cat.Type.class, RegistryKey.CAT_VARIANT, "cattype", "cat types"); Classes.registerClass(catTypeClassInfo .user("cat ?(type|race)s?") .name("Cat Type") diff --git a/src/main/java/ch/njol/skript/entity/ChickenData.java b/src/main/java/ch/njol/skript/entity/ChickenData.java index c4e414e76ef..46176e0697f 100644 --- a/src/main/java/ch/njol/skript/entity/ChickenData.java +++ b/src/main/java/ch/njol/skript/entity/ChickenData.java @@ -1,13 +1,12 @@ package ch.njol.skript.entity; -import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.BukkitUtils; -import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; import ch.njol.util.coll.CollectionUtils; import com.google.common.collect.Iterators; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.entity.Chicken; import org.bukkit.entity.Chicken.Variant; import org.jetbrains.annotations.NotNull; @@ -17,21 +16,11 @@ public class ChickenData extends EntityData { - private static final boolean VARIANTS_ENABLED; - private static final Object[] VARIANTS; + private static final Variant[] VARIANTS; static { - ClassInfo chickenVariantClassInfo = BukkitUtils.getRegistryClassInfo( - "org.bukkit.entity.Chicken$Variant", - "CHICKEN_VARIANT", - "chickenvariant", - "chicken variants" - ); - if (chickenVariantClassInfo == null) { - // Registers a dummy/placeholder class to ensure working operation on MC versions that do not have 'Chicken.Variant' (1.21.4-) - chickenVariantClassInfo = new ClassInfo<>(ChickenVariantDummy.class, "chickenvariant"); - } - Classes.registerClass(chickenVariantClassInfo + var chickenVariantInfo = new RegistryClassInfo<>(Variant.class, RegistryKey.CHICKEN_VARIANT, "chickenvariant", "chicken variants"); + Classes.registerClass(chickenVariantInfo .user("chicken ?variants?") .name("Chicken Variant") .description("Represents the variant of a chicken entity.", @@ -40,38 +29,31 @@ public class ChickenData extends EntityData { .requiredPlugins("Minecraft 1.21.5+") .documentationId("ChickenVariant") ); + VARIANTS = Iterators.toArray(chickenVariantInfo.getSupplier().get(), Variant.class); register(ChickenData.class, "chicken", Chicken.class, "chicken"); - if (Skript.classExists("org.bukkit.entity.Chicken$Variant")) { - VARIANTS_ENABLED = true; - VARIANTS = Iterators.toArray(Classes.getExactClassInfo(Chicken.Variant.class).getSupplier().get(), Chicken.Variant.class); - } else { - VARIANTS_ENABLED = false; - VARIANTS = null; - } } - private @Nullable Object variant = null; + private @Nullable Variant variant = null; public ChickenData() {} - // TODO: When safe, 'variant' should have the type changed to 'Chicken.Variant' when 1.21.6 is minimum supported version - public ChickenData(@Nullable Object variant) { + public ChickenData(@Nullable Variant variant) { this.variant = variant; } @Override protected boolean init(Literal[] exprs, int matchedCodeName, int matchedPattern, ParseResult parseResult) { - if (VARIANTS_ENABLED && exprs[0] != null) { + if (exprs[0] != null) { //noinspection unchecked - variant = ((Literal) exprs[0]).getSingle(); + variant = ((Literal) exprs[0]).getSingle(); } return true; } @Override protected boolean init(@Nullable Class entityClass, @Nullable Chicken chicken) { - if (chicken != null && VARIANTS_ENABLED) { + if (chicken != null) { variant = chicken.getVariant(); } return true; @@ -79,13 +61,11 @@ protected boolean init(@Nullable Class entityClass, @Nullable @Override public void set(Chicken chicken) { - if (VARIANTS_ENABLED) { - Variant variant = (Variant) this.variant; - if (variant == null) - variant = (Variant) CollectionUtils.getRandom(VARIANTS); - assert variant != null; - chicken.setVariant(variant); - } + Variant variant = this.variant; + if (variant == null) + variant = CollectionUtils.getRandom(VARIANTS); + assert variant != null; + chicken.setVariant(variant); } @Override @@ -122,9 +102,4 @@ public boolean isSupertypeOf(EntityData entityData) { return dataMatch(variant, other.variant); } - /** - * A dummy/placeholder class to ensure working operation on MC versions that do not have `Chicken.Variant` - */ - public static class ChickenVariantDummy {} - } diff --git a/src/main/java/ch/njol/skript/entity/CowData.java b/src/main/java/ch/njol/skript/entity/CowData.java index 242a791b064..fa05e6fcb5f 100644 --- a/src/main/java/ch/njol/skript/entity/CowData.java +++ b/src/main/java/ch/njol/skript/entity/CowData.java @@ -1,43 +1,26 @@ package ch.njol.skript.entity; -import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.BukkitUtils; -import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; import ch.njol.util.coll.CollectionUtils; import com.google.common.collect.Iterators; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.entity.Cow; import org.bukkit.entity.Cow.Variant; -import org.bukkit.entity.Entity; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Objects; public class CowData extends EntityData { - private static final boolean VARIANTS_ENABLED; private static final Object[] VARIANTS; - private static final Class COW_CLASS; - private static final @Nullable Method getVariantMethod; - private static final @Nullable Method setVariantMethod; static { - ClassInfo cowVariantClassInfo = BukkitUtils.getRegistryClassInfo( - "org.bukkit.entity.Cow$Variant", - "COW_VARIANT", - "cowvariant", - "cow variants" - ); - if (cowVariantClassInfo == null) { - // Registers a dummy/placeholder class to ensure working operation on MC versions that do not have 'Cow.Variant' (1.21.4-) - cowVariantClassInfo = new ClassInfo<>(CowVariantDummy.class, "cowvariant"); - } - Classes.registerClass(cowVariantClassInfo + var cowVariantInfo = new RegistryClassInfo<>(Variant.class, RegistryKey.COW_VARIANT, "cowvariant", "cow variants"); + Classes.registerClass(cowVariantInfo .user("cow ?variants?") .name("Cow Variant") .description("Represents the variant of a cow entity.", @@ -46,78 +29,53 @@ public class CowData extends EntityData { .requiredPlugins("Minecraft 1.21.5+") .documentationId("CowVariant") ); + VARIANTS = Iterators.toArray(cowVariantInfo.getSupplier().get(), Variant.class); - Class cowClass = null; - - try { - //noinspection unchecked - cowClass = (Class) Class.forName("org.bukkit.entity.Cow"); - } catch (Exception ignored) {} - - COW_CLASS = cowClass; - register(CowData.class, "cow", COW_CLASS, 0, "cow"); - if (Skript.classExists("org.bukkit.entity.Cow$Variant")) { - VARIANTS_ENABLED = true; - VARIANTS = Iterators.toArray(Classes.getExactClassInfo(Cow.Variant.class).getSupplier().get(), Cow.Variant.class); - try { - getVariantMethod = COW_CLASS.getDeclaredMethod("getVariant"); - setVariantMethod = COW_CLASS.getDeclaredMethod("setVariant", Cow.Variant.class); - } catch (Exception e) { - throw new RuntimeException("Could not retrieve get/set variant methods for Cow.", e); - } - } else { - VARIANTS_ENABLED = false; - VARIANTS = null; - getVariantMethod = null; - setVariantMethod = null; - } + register(CowData.class, "cow", Cow.class, 0, "cow"); } - private @Nullable Object variant = null; + private @Nullable Variant variant = null; public CowData() {} - // TODO: When the api-version is 1.21.5, 'variant' should have the type changed to 'Cow.Variant' and reflection can be removed - public CowData(@Nullable Object variant) { + public CowData(@Nullable Variant variant) { this.variant = variant; } @Override protected boolean init(Literal[] exprs, int matchedCodeName, int matchedPattern, ParseResult parseResult) { - if (VARIANTS_ENABLED && exprs[0] != null) { + if (exprs[0] != null) { //noinspection unchecked - variant = ((Literal) exprs[0]).getSingle(); + variant = ((Literal) exprs[0]).getSingle(); } return true; } @Override protected boolean init(@Nullable Class entityClass, @Nullable Cow cow) { - if (cow != null && VARIANTS_ENABLED) { - variant = getVariant(cow); + if (cow != null) { + variant = cow.getVariant(); } return true; } @Override public void set(Cow cow) { - if (VARIANTS_ENABLED) { - Variant variant = (Variant) this.variant; - if (variant == null) - variant = (Variant) CollectionUtils.getRandom(VARIANTS); - assert variant != null; - setVariant(cow, variant); - } + Variant variant = this.variant; + if (variant == null) + variant = (Variant) CollectionUtils.getRandom(VARIANTS); + assert variant != null; + cow.setVariant(variant); } @Override protected boolean match(Cow cow) { - return variant == null || getVariant(cow) == variant; + return variant == null || cow.getVariant() == variant; } @Override public Class getType() { - return COW_CLASS; + return Cow.class; } @Override @@ -144,52 +102,4 @@ public boolean isSupertypeOf(EntityData entityData) { return dataMatch(variant, other.variant); } - /** - * Due to the addition of 'AbstractCow' and 'api-version' being '1.19' - * This helper method is required in order to set the {@link #variant} of the {@link Cow} - * @param cow The {@link Cow} to set the variant - */ - public void setVariant(Cow cow) { - setVariant(cow, variant); - } - - /** - * Due to the addition of 'AbstractCow' and 'api-version' being '1.19' - * This helper method is required in order to set the {@code object} of the {@link Cow} - * @param cow The {@link Cow} to set the variant - * @param object The 'Cow.Variant' - */ - public void setVariant(Cow cow, Object object) { - if (!VARIANTS_ENABLED || setVariantMethod == null) - return; - Entity entity = COW_CLASS.cast(cow); - try { - setVariantMethod.invoke(entity, (Cow.Variant) object); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - /** - * Due to the addition of 'AbstractCow' and 'api-version' being '1.19' - * This helper method is required in order to get the 'Cow.Variant' of the {@link Cow} - * @param cow The {@link Cow} to get the variant - * @return The 'Cow.Variant' - */ - public @Nullable Object getVariant(Cow cow) { - if (!VARIANTS_ENABLED || getVariantMethod == null) - return null; - Entity entity = COW_CLASS.cast(cow); - try { - return getVariantMethod.invoke(entity); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - /** - * A dummy/placeholder class to ensure working operation on MC versions that do not have 'Cow.Variant' - */ - public static class CowVariantDummy {} - } diff --git a/src/main/java/ch/njol/skript/entity/FrogData.java b/src/main/java/ch/njol/skript/entity/FrogData.java index e5021428cd0..f775c2380a9 100644 --- a/src/main/java/ch/njol/skript/entity/FrogData.java +++ b/src/main/java/ch/njol/skript/entity/FrogData.java @@ -1,12 +1,13 @@ package ch.njol.skript.entity; -import ch.njol.skript.bukkitutil.BukkitUtils; -import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Patterns; import ch.njol.util.coll.CollectionUtils; +import com.google.common.collect.Iterators; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.entity.Frog; import org.bukkit.entity.Frog.Variant; import org.jetbrains.annotations.NotNull; @@ -26,16 +27,8 @@ public class FrogData extends EntityData { private static final Variant[] VARIANTS; static { - EntityData.register(FrogData.class, "frog", Frog.class, 0, PATTERNS.getPatterns()); - VARIANTS = new Variant[]{Variant.TEMPERATE, Variant.WARM, Variant.COLD}; - ClassInfo frogVariantClassInfo = BukkitUtils.getRegistryClassInfo( - "org.bukkit.entity.Frog$Variant", - "FROG_VARIANT", - "frogvariant", - "frog variants" - ); - assert frogVariantClassInfo != null; - Classes.registerClass(frogVariantClassInfo + var frogVariantInfo = new RegistryClassInfo<>(Variant.class, RegistryKey.FROG_VARIANT, "frogvariant", "frog variants"); + Classes.registerClass(frogVariantInfo .user("frog ?variants?") .name("Frog Variant") .description("Represents the variant of a frog entity.", @@ -43,6 +36,9 @@ public class FrogData extends EntityData { .since("2.13") .documentationId("FrogVariant") ); + VARIANTS = Iterators.toArray(frogVariantInfo.getSupplier().get(), Variant.class); + + EntityData.register(FrogData.class, "frog", Frog.class, 0, PATTERNS.getPatterns()); } private @Nullable Variant variant = null; diff --git a/src/main/java/ch/njol/skript/entity/PigData.java b/src/main/java/ch/njol/skript/entity/PigData.java index 6fc309307c8..0be397c9173 100644 --- a/src/main/java/ch/njol/skript/entity/PigData.java +++ b/src/main/java/ch/njol/skript/entity/PigData.java @@ -1,8 +1,6 @@ package ch.njol.skript.entity; -import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.BukkitUtils; -import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; @@ -10,7 +8,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import com.google.common.collect.Iterators; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.entity.Pig; +import org.bukkit.entity.Pig.Variant; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,8 +18,7 @@ public class PigData extends EntityData { - private static final boolean VARIANTS_ENABLED; - private static final Object[] VARIANTS; + private static final Variant[] VARIANTS; private static final Patterns PATTERNS = new Patterns<>(new Object[][]{ {"pig", Kleenean.UNKNOWN}, {"saddled pig", Kleenean.TRUE}, @@ -27,17 +26,8 @@ public class PigData extends EntityData { }); static { - ClassInfo pigVariantClassInfo = BukkitUtils.getRegistryClassInfo( - "org.bukkit.entity.Pig$Variant", - "PIG_VARIANT", - "pigvariant", - "pig variants" - ); - if (pigVariantClassInfo == null) { - // Registers a dummy/placeholder class to ensure working operation on MC versions that do not have 'Pig.Variant' (1.21.4-) - pigVariantClassInfo = new ClassInfo<>(PigVariantDummy.class, "pigvariant"); - } - Classes.registerClass(pigVariantClassInfo + var pigVariantInfo = new RegistryClassInfo<>(Variant.class, RegistryKey.PIG_VARIANT, "pigvariant", "pig variants"); + Classes.registerClass(pigVariantInfo .user("pig ?variants?") .name("Pig Variant") .description("Represents the variant of a pig entity.", @@ -45,24 +35,17 @@ public class PigData extends EntityData { .since("2.12") .requiredPlugins("Minecraft 1.21.5+") .documentationId("PigVariant")); + VARIANTS = Iterators.toArray(pigVariantInfo.getSupplier().get(), Pig.Variant.class); register(PigData.class, "pig", Pig.class, 0, PATTERNS.getPatterns()); - if (Skript.classExists("org.bukkit.entity.Pig$Variant")) { - VARIANTS_ENABLED = true; - VARIANTS = Iterators.toArray(Classes.getExactClassInfo(Pig.Variant.class).getSupplier().get(), Pig.Variant.class); - } else { - VARIANTS_ENABLED = false; - VARIANTS = null; - } } private Kleenean saddled = Kleenean.UNKNOWN; - private @Nullable Object variant = null; + private @Nullable Variant variant; public PigData() {} - // TODO: When safe, 'variant' should have the type changed to 'Pig.Variant' when 1.21.5 is minimum supported version - public PigData(@Nullable Kleenean saddled, @Nullable Object variant) { + public PigData(@Nullable Kleenean saddled, @Nullable Variant variant) { this.saddled = saddled != null ? saddled : Kleenean.UNKNOWN; this.variant = variant; super.codeNameIndex = PATTERNS.getMatchedPattern(this.saddled, 0).orElse(0); @@ -71,7 +54,7 @@ public PigData(@Nullable Kleenean saddled, @Nullable Object variant) { @Override protected boolean init(Literal[] exprs, int matchedCodeName, int matchedPattern, ParseResult parseResult) { saddled = PATTERNS.getInfo(matchedCodeName); - if (VARIANTS_ENABLED && exprs[0] != null) { + if (exprs[0] != null) { //noinspection unchecked variant = ((Literal) exprs[0]).getSingle(); } @@ -83,8 +66,7 @@ protected boolean init(@Nullable Class entityClass, @Nullable Pig if (pig != null) { saddled = Kleenean.get(pig.hasSaddle()); super.codeNameIndex = PATTERNS.getMatchedPattern(saddled, 0).orElse(0); - if (VARIANTS_ENABLED) - variant = pig.getVariant(); + variant = pig.getVariant(); } return true; } @@ -92,11 +74,9 @@ protected boolean init(@Nullable Class entityClass, @Nullable Pig @Override public void set(Pig pig) { pig.setSaddle(saddled.isTrue()); - if (VARIANTS_ENABLED) { - Object finalVariant = variant != null ? variant : CollectionUtils.getRandom(VARIANTS); - assert finalVariant != null; - pig.setVariant((Pig.Variant) finalVariant); - } + Variant finalVariant = variant != null ? variant : CollectionUtils.getRandom(VARIANTS); + assert finalVariant != null; + pig.setVariant(finalVariant); } @Override @@ -139,9 +119,4 @@ public boolean isSupertypeOf(EntityData entityData) { return variant == null || variant == other.variant; } - /** - * A dummy/placeholder class to ensure working operation on MC versions that do not have `Pig.Variant` - */ - public static class PigVariantDummy {} - } diff --git a/src/main/java/ch/njol/skript/entity/WolfData.java b/src/main/java/ch/njol/skript/entity/WolfData.java index a1f0613212c..3530ac29bec 100644 --- a/src/main/java/ch/njol/skript/entity/WolfData.java +++ b/src/main/java/ch/njol/skript/entity/WolfData.java @@ -1,8 +1,6 @@ package ch.njol.skript.entity; -import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.BukkitUtils; -import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; @@ -11,8 +9,10 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import com.google.common.collect.Iterators; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.DyeColor; import org.bukkit.entity.Wolf; +import org.bukkit.entity.Wolf.Variant; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,21 +30,11 @@ public record WolfStates(Kleenean angry, Kleenean tamed) {} {"peaceful wolf", new WolfStates(Kleenean.FALSE, Kleenean.UNKNOWN)} }); - private static final boolean VARIANTS_ENABLED; - private static final Object[] VARIANTS; + private static final Variant[] VARIANTS; static { - ClassInfo wolfVariantClassInfo = BukkitUtils.getRegistryClassInfo( - "org.bukkit.entity.Wolf$Variant", - "WOLF_VARIANT", - "wolfvariant", - "wolf variants" - ); - if (wolfVariantClassInfo == null) { - // Registers a dummy/placeholder class to ensure working operation on MC versions that do not have 'Wolf.Variant' (1.20.4-) - wolfVariantClassInfo = new ClassInfo<>(WolfVariantDummy.class, "wolfvariant"); - } - Classes.registerClass(wolfVariantClassInfo + var wolfVariantInfo = new RegistryClassInfo<>(Variant.class, RegistryKey.WOLF_VARIANT, "wolfvariant", "wolf variants"); + Classes.registerClass(wolfVariantInfo .user("wolf ?variants?") .name("Wolf Variant") .description("Represents the variant of a wolf entity.", @@ -52,18 +42,12 @@ public record WolfStates(Kleenean angry, Kleenean tamed) {} .since("2.10") .requiredPlugins("Minecraft 1.21+") .documentationId("WolfVariant")); + VARIANTS = Iterators.toArray(wolfVariantInfo.getSupplier().get(), Variant.class); EntityData.register(WolfData.class, "wolf", Wolf.class, 0, PATTERNS.getPatterns()); - if (Skript.classExists("org.bukkit.entity.Wolf$Variant")) { - VARIANTS_ENABLED = true; - VARIANTS = Iterators.toArray(Classes.getExactClassInfo(Wolf.Variant.class).getSupplier().get(), Wolf.Variant.class); - } else { - VARIANTS_ENABLED = false; - VARIANTS = null; - } } - private @Nullable Object variant = null; + private @Nullable Variant variant = null; private @Nullable DyeColor collarColor = null; private Kleenean isAngry = Kleenean.UNKNOWN; private Kleenean isTamed = Kleenean.UNKNOWN; @@ -94,9 +78,9 @@ protected boolean init(Literal[] exprs, int matchedCodeName, int matchedPatte assert state != null; isAngry = state.angry; isTamed = state.tamed; - if (exprs[0] != null && VARIANTS_ENABLED) { + if (exprs[0] != null) { //noinspection unchecked - variant = ((Literal) exprs[0]).getSingle(); + variant = ((Literal) exprs[0]).getSingle(); } if (exprs[1] != null) { //noinspection unchecked @@ -111,8 +95,7 @@ protected boolean init(@Nullable Class entityClass, @Nullable Wo isAngry = Kleenean.get(wolf.isAngry()); isTamed = Kleenean.get(wolf.isTamed()); collarColor = wolf.getCollarColor(); - if (VARIANTS_ENABLED) - variant = wolf.getVariant(); + variant = wolf.getVariant(); super.codeNameIndex = PATTERNS.getMatchedPattern(new WolfStates(isAngry, isTamed), 0).orElse(0); } return true; @@ -124,11 +107,9 @@ public void set(Wolf wolf) { wolf.setTamed(isTamed.isTrue()); if (collarColor != null) wolf.setCollarColor(collarColor); - if (VARIANTS_ENABLED) { - Object variantSet = variant != null ? variant : CollectionUtils.getRandom(VARIANTS); - assert variantSet != null; - wolf.setVariant((Wolf.Variant) variantSet); - } + Variant variantSet = variant != null ? variant : CollectionUtils.getRandom(VARIANTS); + assert variantSet != null; + wolf.setVariant(variantSet); } @Override @@ -158,8 +139,7 @@ protected int hashCode_i() { result = prime * result + isAngry.hashCode(); result = prime * result + isTamed.hashCode(); result = prime * result + Objects.hashCode(collarColor); - if (VARIANTS_ENABLED) - result = prime * result + Objects.hashCode(variant); + result = prime * result + Objects.hashCode(variant); return result; } @@ -189,9 +169,4 @@ public boolean isSupertypeOf(EntityData entityData) { return dataMatch(variant, other.variant); } - /** - * A dummy/placeholder class to ensure working operation on MC versions that do not have `Wolf.Variant` - */ - public static class WolfVariantDummy {}; - } diff --git a/src/main/java/ch/njol/skript/lang/function/Function.java b/src/main/java/ch/njol/skript/lang/function/Function.java index ca9ccca28b1..dd54ea41184 100644 --- a/src/main/java/ch/njol/skript/lang/function/Function.java +++ b/src/main/java/ch/njol/skript/lang/function/Function.java @@ -91,10 +91,10 @@ public Class type() { public final T @Nullable [] execute(Object[][] params) { FunctionEvent event = new FunctionEvent<>(this); - // Call function event only if requested by addon - // Functions may be called VERY often, so this might have performance impact if (Functions.callFunctionEvents) Bukkit.getPluginManager().callEvent(event); + org.skriptlang.skript.common.function.Function.eventRegistry().events(Function.CallEvent.class) + .forEach(e -> e.onCall(this)); // Parameters taken by the function. Parameters parameters = sign.parameters(); @@ -110,7 +110,7 @@ public Class type() { int i = 0; // Execute parameters or default value expressions for (org.skriptlang.skript.common.function.Parameter parameter : parameters.all()) { - Object[] parameterValue = parameter.hasModifier(Modifier.KEYED) ? convertToKeyed(parameterValues[i]) : parameterValues[i]; + Object[] parameterValue = parameter.hasModifier(Modifier.Keyed.class) ? convertToKeyed(parameterValues[i]) : parameterValues[i]; Expression defaultValueExpr; if (parameter instanceof Parameter script) { @@ -122,7 +122,7 @@ public Class type() { } // see https://github.com/SkriptLang/Skript/pull/8135 - if ((parameterValues[i] == null || parameterValues[i].length == 0) && parameter.hasModifier(Modifier.KEYED) && defaultValueExpr != null) { + if ((parameterValues[i] == null || parameterValues[i].length == 0) && parameter.hasModifier(Modifier.Keyed.class) && defaultValueExpr != null) { Object[] defaultValue = defaultValueExpr.getArray(event); if (defaultValue.length == 1) { parameterValue = KeyedValue.zip(defaultValue, null); diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index d8cc497288b..3fb38ebe4cc 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -4,8 +4,6 @@ import ch.njol.skript.SkriptAPIException; import ch.njol.skript.config.Node; import ch.njol.skript.lang.*; -import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; -import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; @@ -15,11 +13,14 @@ import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.common.function.Function; import org.skriptlang.skript.common.function.FunctionReference.Argument; import org.skriptlang.skript.common.function.FunctionReference.ArgumentType; +import org.skriptlang.skript.common.function.FunctionRegistry.Retrieval; +import org.skriptlang.skript.common.function.FunctionRegistry.RetrievalResult; import org.skriptlang.skript.common.function.Parameter; import org.skriptlang.skript.common.function.Parameter.Modifier; -import org.skriptlang.skript.common.function.Parameter.Modifier.RangedModifier; +import org.skriptlang.skript.common.function.Signature; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.util.Executable; @@ -46,13 +47,13 @@ public class FunctionReference implements Contract, Executable { * Signature of referenced function. If {@link #validateFunction(boolean)} * succeeds, this is not null. */ - private @Nullable Signature signature; + private @Nullable org.skriptlang.skript.common.function.Signature signature; /** * Actual function reference. Null before the function is called for first * time. */ - private @Nullable Function function; + private @Nullable org.skriptlang.skript.common.function.Function function; /** * If all function parameters can be condensed to a single list. @@ -116,7 +117,7 @@ public boolean validateParameterArity(boolean first) { return false; // Not enough parameters - return parameters.length >= sign.getMinParameters(); + return parameters.length >= sign.parameters().minCount(); } private Class[] parameterTypes; @@ -158,8 +159,7 @@ public boolean validateFunction(boolean first) { // Validate that return types are what caller expects they are Class[] expectedReturnTypes = this.returnTypes; if (expectedReturnTypes != null) { - Class candidateReturnType = sign.returnType(); - if (candidateReturnType == null) { + if (!sign.hasModifier(Signature.Modifier.Returns.class)) { if (first) { Skript.error("The function '" + stringified + "' doesn't return any value."); } else { @@ -170,6 +170,7 @@ public boolean validateFunction(boolean first) { return false; } + Class candidateReturnType = sign.getModifier(Signature.Modifier.Returns.class).type(); if (!Converters.converterExists(candidateReturnType, expectedReturnTypes)) { if (first) { Skript.error("The returned value of the function '" + stringified + "', " + candidateReturnType + ", is " + SkriptParser.notOfType(expectedReturnTypes) + "."); @@ -191,16 +192,16 @@ public boolean validateFunction(boolean first) { } // Validate parameter count - singleListParam = sign.getMaxParameters() == 1 && !sign.parameters().getFirst().isSingle(); + singleListParam = sign.parameters().maxCount() == 1 && !sign.parameters().getFirst().isSingle(); if (!singleListParam) { // Check that parameter count is within allowed range // Too many parameters - if (parameters.length > sign.getMaxParameters()) { + if (parameters.length > sign.parameters().maxCount()) { if (first) { - if (sign.getMaxParameters() == 0) { + if (sign.parameters().maxCount() == 0) { Skript.error("The function '" + stringified + "' has no arguments, but " + parameters.length + " are given." + " To call a function without parameters, just write the function name followed by '()', e.g. 'func()'."); } else { - Skript.error("The function '" + stringified + "' has only " + sign.getMaxParameters() + " argument" + (sign.getMaxParameters() == 1 ? "" : "s") + "," + Skript.error("The function '" + stringified + "' has only " + sign.parameters().maxCount() + " argument" + (sign.parameters().maxCount() == 1 ? "" : "s") + "," + " but " + parameters.length + " are given." + " If you want to use lists in function calls, you have to use additional parentheses, e.g. 'give(player, (iron ore and gold ore))'"); } @@ -214,9 +215,9 @@ public boolean validateFunction(boolean first) { } // Not enough parameters - if (parameters.length < sign.getMinParameters()) { + if (parameters.length < sign.parameters().minCount()) { if (first) { - Skript.error("The function '" + stringified + "' requires at least " + sign.getMinParameters() + " argument" + (sign.getMinParameters() == 1 ? "" : "s") + "," + Skript.error("The function '" + stringified + "' requires at least " + sign.parameters().minCount() + " argument" + (sign.parameters().minCount() == 1 ? "" : "s") + "," + " but only " + parameters.length + " " + (parameters.length == 1 ? "is" : "are") + " given."); } else { Skript.error("The function '" + stringified + "' was redefined with a different, incompatible amount of arguments, but is still used in other script(s)." @@ -265,11 +266,11 @@ public boolean validateFunction(boolean first) { } // check ranged parameters - if (signatureParam.hasModifier(Modifier.RANGED) && exprParam instanceof Literal literalParam) { - RangedModifier range = signatureParam.getModifier(RangedModifier.class); + if (signatureParam.hasModifier(Modifier.Ranged.class) && exprParam instanceof Literal literalParam) { + Modifier.Ranged range = signatureParam.getModifier(Modifier.Ranged.class); if (!range.inRange(literalParam.getArray())) { Skript.error("The argument '" + signatureParam.name() +"' only accepts values between " - + Classes.toString(range.getMin()) + " and " + Classes.toString(range.getMax()) + ". " + + Classes.toString(range.min()) + " and " + Classes.toString(range.max()) + ". " + "Provided: " + literalParam.toString(null, Skript.debug())); return false; } @@ -289,9 +290,9 @@ public boolean validateFunction(boolean first) { .map(it -> new Argument<>(ArgumentType.UNNAMED, null, it)) .toArray(Argument[]::new); - sign.calls().add(new org.skriptlang.skript.common.function.FunctionReference<>(script, functionName, signature, arguments)); + sign.addCall(new org.skriptlang.skript.common.function.FunctionReference<>(script, functionName, signature, arguments)); - Contract contract = sign.getContract(); + Contract contract = sign.contract(); if (contract != null) this.contract = contract; @@ -322,7 +323,7 @@ private Signature getRegisteredSignature() { functionName, Arrays.toString(Arrays.stream(parameterTypes).map(Class::getSimpleName).toArray())); } - Retrieval> attempt = FunctionRegistry.getRegistry().getSignature(script, functionName, parameterTypes); + Retrieval> attempt = Skript.instance().registry(org.skriptlang.skript.common.function.FunctionRegistry.class).getSignature(script, functionName, parameterTypes); if (attempt.result() == RetrievalResult.EXACT) { return attempt.retrieved(); } @@ -345,8 +346,7 @@ private Function getRegisteredFunction() { functionName, Arrays.toString(Arrays.stream(parameterTypes).map(Class::getSimpleName).toArray())); } - Retrieval> attempt = FunctionRegistry.getRegistry().getFunction(script, functionName, parameterTypes); - + Retrieval> attempt = Skript.instance().registry(org.skriptlang.skript.common.function.FunctionRegistry.class).getFunction(script, functionName, parameterTypes); if (attempt.result() == RetrievalResult.EXACT) { return attempt.retrieved(); } @@ -396,7 +396,8 @@ public boolean resetReturnValue() { } // Execute the function - return function.execute(params); + //noinspection unchecked + return (T[]) ((ch.njol.skript.lang.function.Function) function).execute(params); } private Object[] evaluateSingleListParameter(Parameter parameter, Expression[] arguments, Event event) { @@ -407,7 +408,7 @@ private Object[] evaluateSingleListParameter(Parameter parameter, Expression< //noinspection unchecked,rawtypes return parameter.evaluate((Expression) arguments[0], event); - if (!parameter.hasModifier(Modifier.KEYED)) { + if (!parameter.hasModifier(Modifier.Keyed.class)) { List list = new ArrayList<>(); for (Expression argument : arguments) //noinspection unchecked,rawtypes @@ -495,7 +496,8 @@ public T[] execute(Event event, Object... arguments) { // We shouldn't trust the caller provided an array of arrays Object[][] consigned = consign(arguments); try { - return function.execute(consigned); + //noinspection unchecked + return (T[]) ((ch.njol.skript.lang.function.Function) function).execute(consigned); } finally { this.resetReturnValue(); } @@ -514,10 +516,10 @@ static Object[][] consign(Object... arguments) { } - private void ambiguousError(Class[][] conflictingArgs) { + private void ambiguousError(Collection>> conflictingArgs) { List parts = new ArrayList<>(); - for (Class[] args : conflictingArgs) { - String argNames = Arrays.stream(args).map(arg -> { + for (SequencedCollection> args : conflictingArgs) { + String argNames = args.stream().map(arg -> { String name = Classes.getExactClassName(arg); if (name == null) { diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java index b26a33a2c58..0da7ef916ec 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java @@ -1,37 +1,35 @@ package ch.njol.skript.lang.function; -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptAPIException; -import ch.njol.skript.util.Utils; -import com.google.common.base.MoreObjects; -import com.google.common.base.Preconditions; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; -import org.skriptlang.skript.common.function.Parameter; -import org.skriptlang.skript.common.function.Parameter.Modifier; -import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.util.Registry; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; +import java.util.Collection; +import java.util.Set; import java.util.stream.Collectors; /** - * A registry for functions. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry} instead. */ -@ApiStatus.Internal +@Deprecated(forRemoval = true, since = "INSERT VERSION") public final class FunctionRegistry implements Registry> { + private static org.skriptlang.skript.common.function.FunctionRegistry newRegistry; + + @ApiStatus.Internal + public static void setNewRegistry(org.skriptlang.skript.common.function.FunctionRegistry newRegistry) { + FunctionRegistry.newRegistry = newRegistry; + } + private static FunctionRegistry registry; /** - * Gets the global function registry. - * - * @return The global function registry. + * @deprecated Use {@link org.skriptlang.skript.addon.SkriptAddon#registry(Class)} + * and {@link org.skriptlang.skript.common.function.FunctionRegistry} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static FunctionRegistry getRegistry() { if (registry == null) { registry = new FunctionRegistry(); @@ -40,175 +38,38 @@ public static FunctionRegistry getRegistry() { } /** - * The pattern for a valid function name. - * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. - */ - final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile(Functions.functionNamePattern); - - /** - * The namespace for registered global functions. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#elements()} instead. */ - private final NamespaceIdentifier GLOBAL_NAMESPACE = new NamespaceIdentifier(null); - - /** - * All registered namespaces. - */ - private final Map namespaces = new ConcurrentHashMap<>(); - + @Deprecated(forRemoval = true, since = "INSERT VERSION") @Override public @Unmodifiable @NotNull Collection> elements() { - Set> functions = new HashSet<>(); - - for (Namespace namespace : namespaces.values()) { - functions.addAll(namespace.functions.values()); - } - - return Collections.unmodifiableSet(functions); + return newRegistry.elements().stream() + .map(it -> (Function) it) + .collect(Collectors.toUnmodifiableSet()); } /** - * Registers a signature. - *

- * Attempting to register a local signature in the global namespace, or a global signature in - * a local namespace, will throw an {@link IllegalArgumentException}. - * If {@code namespace} is null, will register this signature globally, - * only if the signature is global. - *

- * - * @param namespace The namespace to register the signature in. - * Usually represents the path of the script this signature is registered in. - * @param signature The signature to register. - * @throws SkriptAPIException if a signature with the same name and parameters is already registered - * in this namespace. - * @throws IllegalArgumentException if the signature is global and namespace is not null, or - * if the signature is local and namespace is null. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#register(String, org.skriptlang.skript.common.function.Signature)} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public void register(@Nullable String namespace, @NotNull Signature signature) { - Preconditions.checkNotNull(signature, "signature cannot be null"); - if (signature.isLocal() && namespace == null) { - throw new IllegalArgumentException("Cannot register a local signature in the global namespace"); - } - if (!signature.isLocal() && namespace != null) { - throw new IllegalArgumentException("Cannot register a global signature in a local namespace"); - } - - Skript.debug("Registering signature '%s'", signature.getName()); - - // namespace - NamespaceIdentifier namespaceId; - if (namespace != null) { - namespaceId = new NamespaceIdentifier(namespace); + if (namespace == null) { + newRegistry.register(signature); } else { - namespaceId = GLOBAL_NAMESPACE; - } - - Namespace ns = namespaces.computeIfAbsent(namespaceId, n -> new Namespace()); - FunctionIdentifier identifier = FunctionIdentifier.of(signature); - - // register - // since we are getting a set and then updating it, - // avoid race conditions by ensuring only one thread can access this namespace for this operation - synchronized (ns) { - Set identifiersWithName = ns.identifiers.computeIfAbsent(identifier.name, s -> new HashSet<>()); - boolean exists = identifiersWithName.add(identifier); - if (!exists) { - alreadyRegisteredError(signature.getName(), identifier, namespaceId); - } - } - - Signature existing = ns.signatures.putIfAbsent(identifier, signature); - if (existing != null) { - alreadyRegisteredError(signature.getName(), identifier, namespaceId); + newRegistry.register(namespace, signature); } } /** - * Registers a function. - *

- * Attempting to register a local function in the global namespace, or a global function in - * a local namespace, will throw an {@link IllegalArgumentException}. - * If {@code namespace} is null, will register this function globally, - * only if the function is global. - *

- * - * @param namespace The namespace to register the function in. - * Usually represents the path of the script this function is registered in. - * @param function The function to register. - * @throws SkriptAPIException if the function name is invalid or if - * a function with the same name and parameters is already registered - * in this namespace. - * @throws IllegalArgumentException if the function is global and namespace is not null, or - * if the function is local and namespace is null. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#register(String, org.skriptlang.skript.common.function.Function)} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public void register(@Nullable String namespace, @NotNull Function function) { - Preconditions.checkNotNull(function, "function cannot be null"); - if (function.getSignature().isLocal() && namespace == null) { - throw new IllegalArgumentException("Cannot register a local function in the global namespace"); - } - if (!function.getSignature().isLocal() && namespace != null) { - throw new IllegalArgumentException("Cannot register a global function in a local namespace"); - } - Skript.debug("Registering function '%s'", function.getName()); - - String name = function.getName(); - if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { - throw new SkriptAPIException("Invalid function name '" + name + "'"); - } - - // namespace - NamespaceIdentifier namespaceId; - if (namespace != null) { - namespaceId = new NamespaceIdentifier(namespace); + if (namespace == null) { + newRegistry.register(function); } else { - namespaceId = GLOBAL_NAMESPACE; - } - - FunctionIdentifier identifier = FunctionIdentifier.of(function.getSignature()); - if (!signatureExists(namespaceId, identifier)) { - register(namespace, function.getSignature()); - } - - Namespace ns = namespaces.computeIfAbsent(namespaceId, n -> new Namespace()); - - Function existing = ns.functions.putIfAbsent(identifier, function); - if (existing != null) { - alreadyRegisteredError(name, identifier, namespaceId); - } - } - - private static void alreadyRegisteredError(String name, FunctionIdentifier identifier, NamespaceIdentifier namespace) { - throw new SkriptAPIException("Function '%s' with parameters %s is already registered in %s" - .formatted(name, Arrays.toString(Arrays.stream(identifier.args).map(Class::getSimpleName).toArray()), - namespace)); - } - - /** - * Checks if a function with the given name and arguments exists in the namespace. - * - * @param namespace The namespace to check in. - * @param identifier The identifier of the function. - * @return True if a function with the given name and arguments exists in the namespace, false otherwise. - */ - private boolean signatureExists(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier identifier) { - Preconditions.checkNotNull(namespace, "namespace cannot be null"); - Preconditions.checkNotNull(identifier, "identifier cannot be null"); - - Namespace ns = namespaces.get(namespace); - if (ns == null) { - return false; - } - - if (!ns.identifiers.containsKey(identifier.name)) { - return false; + newRegistry.register(namespace, function); } - - for (FunctionIdentifier other : ns.identifiers.get(identifier.name)) { - if (identifier.equals(other)) { - return true; - } - } - - return false; } /** @@ -262,109 +123,58 @@ public enum RetrievalResult { * @param conflictingArgs The conflicting arguments if {@code result} is {@code AMBIGUOUS}. */ public record Retrieval( - @NotNull RetrievalResult result, - T retrieved, - Class[][] conflictingArgs + @NotNull RetrievalResult result, + T retrieved, + Class[][] conflictingArgs ) { } /** - * Gets a function from a script. If no local function is found, checks for global functions. - * If {@code namespace} is null, only global functions will be checked. - * - * @param namespace The namespace to get the function from. - * Usually represents the path of the script this function is registered in. - * @param name The name of the function. - * @param args The types of the arguments of the function. - * @return Information related to the attempt to get the specified function, - * stored in a {@link Retrieval} object. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getFunction(String, String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public @NotNull Retrieval> getFunction( - @Nullable String namespace, - @NotNull String name, - @NotNull Class... args + @Nullable String namespace, + @NotNull String name, + @NotNull Class... args ) { - Retrieval> attempt = null; - if (namespace != null) { - attempt = getFunction(new NamespaceIdentifier(namespace), - FunctionIdentifier.of(name, true, args)); - } - if (attempt == null || attempt.result() == RetrievalResult.NOT_REGISTERED) { - attempt = getFunction(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); - } - return attempt; - } - - /** - * Gets a function from a namespace. - * - * @param namespace The namespace to get the function from. - * Usually represents the path of the script this function is registered in. - * @param provided The provided identifier of the function. - * @return Information related to the attempt to get the specified function, - * stored in a {@link Retrieval} object. - */ - private @NotNull Retrieval> getFunction(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier provided) { - Preconditions.checkNotNull(namespace, "namespace cannot be null"); - Preconditions.checkNotNull(provided, "provided cannot be null"); - - Namespace ns = namespaces.getOrDefault(namespace, new Namespace()); - Set existing = ns.identifiers.get(provided.name); - if (existing == null) { - Skript.debug("No functions named '%s' exist in the '%s' namespace", provided.name, namespace.name); - return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); - } + org.skriptlang.skript.common.function.FunctionRegistry.Retrieval> retrieval; - Set candidates = candidates(provided, existing, false); - if (candidates.isEmpty()) { - Skript.debug("Failed to find a function for '%s'", provided.name); - return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); - } else if (candidates.size() == 1) { - if (Skript.debug()) { - Skript.debug("Matched function for '%s': %s", provided.name, candidates.stream().findAny().orElse(null)); - } - return new Retrieval<>(RetrievalResult.EXACT, - ns.functions.get(candidates.stream().findAny().orElse(null)), - null); + if (namespace == null) { + retrieval = newRegistry.getFunction(name, args); } else { - if (Skript.debug()) { - String options = candidates.stream().map(Record::toString).collect(Collectors.joining(", ")); - Skript.debug("Failed to match an exact function for '%s'", provided.name); - Skript.debug("Identifier: %s", provided); - Skript.debug("Options: %s", options); - } - return new Retrieval<>(RetrievalResult.AMBIGUOUS, - null, - candidates.stream() - .map(FunctionIdentifier::args) - .toArray(Class[][]::new)); + retrieval = newRegistry.getFunction(namespace, name, args); } + + return new Retrieval<>(RetrievalResult.valueOf(retrieval.result().name()), + (Function) retrieval.retrieved(), + retrieval.conflictingArgs().stream() + .map(it -> it.toArray(Class[]::new)) + .toArray(Class[][]::new)); } /** - * Gets the signature for a function with the given name and arguments. If no local function is found, - * checks for global functions. If {@code namespace} is null, only global signatures will be checked. - * - * @param namespace The namespace to get the function from. - * Usually represents the path of the script this function is registered in. - * @param name The name of the function. - * @param args The types of the arguments of the function. - * @return The signature for the function with the given name and argument types, or null if no such function exists. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getSignature(String, String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public Retrieval> getSignature( - @Nullable String namespace, - @NotNull String name, - @NotNull Class... args + @Nullable String namespace, + @NotNull String name, + @NotNull Class... args ) { - Retrieval> attempt = null; - if (namespace != null) { - attempt = getSignature(new NamespaceIdentifier(namespace), - FunctionIdentifier.of(name, true, args), false); - } - if (attempt == null || attempt.result() == RetrievalResult.NOT_REGISTERED) { - attempt = getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args), false); + org.skriptlang.skript.common.function.FunctionRegistry.Retrieval> retrieval; + + if (namespace == null) { + retrieval = newRegistry.getSignature(name, args); + } else { + retrieval = newRegistry.getSignature(namespace, name, args); } - return attempt; + + return new Retrieval<>(RetrievalResult.valueOf(retrieval.result().name()), + (Signature) retrieval.retrieved(), + retrieval.conflictingArgs().stream() + .map(it -> it.toArray(Class[]::new)) + .toArray(Class[][]::new)); } /** @@ -383,378 +193,49 @@ public Retrieval> getSignature( * @return The signature for the function with the given name and argument types, or null if no such function exists. */ Retrieval> getExactSignature( - @Nullable String namespace, - @NotNull String name, - @NotNull Class... args + @Nullable String namespace, + @NotNull String name, + @NotNull Class... args ) { - Retrieval> attempt = null; - if (namespace != null) { - attempt = getSignature(new NamespaceIdentifier(namespace), - FunctionIdentifier.of(name, true, args), true); - } - - if (attempt == null || attempt.result() == RetrievalResult.NOT_REGISTERED) { - attempt = getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args), true); - } - return attempt; - } - - /** - * Gets every signature with the name {@code name}. - * This includes global functions and, if {@code namespace} is not null, functions under that namespace (if valid). - * @param namespace The additional namespace to obtain signatures from. - * Usually represents the path of the script this function is registered in. - * @param name The name of the signature(s) to obtain. - * @return A list of all signatures named {@code name}. - */ - public @Unmodifiable @NotNull Set> getSignatures(@Nullable String namespace, @NotNull String name) { - Preconditions.checkNotNull(name, "name cannot be null"); - - Map> total = new HashMap<>(); - - // obtain all local functions of "name" - if (namespace != null) { - Namespace local = namespaces.getOrDefault(new NamespaceIdentifier(namespace), new Namespace()); - - for (FunctionIdentifier identifier : local.identifiers.getOrDefault(name, Collections.emptySet())) { - total.putIfAbsent(identifier, local.signatures.get(identifier)); - } - } + org.skriptlang.skript.common.function.FunctionRegistry.Retrieval> retrieval; - // obtain all global functions of "name" - Namespace global = namespaces.getOrDefault(GLOBAL_NAMESPACE, new Namespace()); - for (FunctionIdentifier identifier : global.identifiers.getOrDefault(name, Collections.emptySet())) { - total.putIfAbsent(identifier, global.signatures.get(identifier)); - } - - return Set.copyOf(total.values()); - } - - /** - * Gets the signature for a function with the given name and arguments. - * - * @param namespace The namespace to get the function from. - * @param provided The provided identifier of the function. - * @param exact When false, will convert arguments to different types to attempt to find a match. - * When true, will not convert arguments. - * @return The signature for the function with the given name and argument types, or null if no such signature exists - * in the specified namespace. - */ - private Retrieval> getSignature(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier provided, boolean exact) { - Preconditions.checkNotNull(namespace, "namespace cannot be null"); - Preconditions.checkNotNull(provided, "provided cannot be null"); - - Namespace ns = namespaces.getOrDefault(namespace, new Namespace()); - if (!ns.identifiers.containsKey(provided.name)) { - Skript.debug("No signatures named '%s' exist in the '%s' namespace", provided.name, namespace.name); - return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); - } - - Set candidates = candidates(provided, ns.identifiers.get(provided.name), exact); - if (candidates.isEmpty()) { - Skript.debug("Failed to find a signature for '%s'", provided.name); - return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); - } else if (candidates.size() == 1) { - if (Skript.debug()) { - Skript.debug("Matched signature for '%s': %s", - provided.name, ns.signatures.get(candidates.stream().findAny().orElse(null))); - } - return new Retrieval<>(RetrievalResult.EXACT, - ns.signatures.get(candidates.stream().findAny().orElse(null)), - null); + if (namespace == null) { + retrieval = newRegistry.getSignature(name, args); } else { - if (Skript.debug()) { - String options = candidates.stream().map(Record::toString).collect(Collectors.joining(", ")); - Skript.debug("Failed to match an exact signature for '%s'", provided.name); - Skript.debug("Identifier: %s", provided); - Skript.debug("Options: %s", options); - } - return new Retrieval<>(RetrievalResult.AMBIGUOUS, - null, - candidates.stream() - .map(FunctionIdentifier::args) - .toArray(Class[][]::new)); - } - } - - /** - * Returns an unmodifiable list of candidates for the provided function identifier. - * - * @param provided The provided function. - * @param existing The existing functions with the same name. - * @param exact When false, will convert arguments to different types to attempt to find a match. - * When true, will not convert arguments. - * @return An unmodifiable list of candidates for the provided function. - */ - private static @Unmodifiable @NotNull Set candidates( - @NotNull FunctionIdentifier provided, - Set existing, - boolean exact - ) { - Set candidates = new HashSet<>(); - - candidates: - for (FunctionIdentifier candidate : existing) { - // by this point, all candidates have matching names - - if (Arrays.stream(candidate.args).filter(Class::isArray).count() == 1 - && candidate.args.length == 1 - && candidate.args[0].isArray()) { - // if a function has single list value param, check all types - // make sure all types in the passed array are valid for the array parameter - Class arrayType = candidate.args[0].componentType(); - for (Class arrayArg : provided.args) { - arrayArg = Utils.getComponentType(arrayArg); - - if (!Converters.converterExists(arrayArg, arrayType)) { - continue candidates; - } - } - - candidates.add(candidate); - continue; - } - - // if argument counts are not possible, skip - if (provided.args.length > candidate.args.length - || provided.args.length < candidate.minArgCount) { - continue; - } - - // if the types of the provided arguments do not match the candidate arguments, skip - for (int i = 0; i < provided.args.length; i++) { - // allows single passed values to still match array type in candidate (e.g. clamp) - Class providedType = Utils.getComponentType(provided.args[i]); - - Class candidateType = Utils.getComponentType(candidate.args[i]); - Class providedArg = provided.args[i]; - if (exact) { - if (providedArg != candidateType) { - continue candidates; - } - } else { - if (!Converters.converterExists(providedType, candidateType)) { - continue candidates; - } - } - } - - candidates.add(candidate); - } - - if (candidates.size() <= 1) { - // if there is only one candidate, then return without trying to convert - return Collections.unmodifiableSet(candidates); + retrieval = newRegistry.getSignature(namespace, name, args); } - // let overloaded(Long, Long) and overloaded(String, String) be two functions. - // the code below allows overloaded(1, {_x}) to match Long, Long and avoid String, String, - // and allow overloaded({_x}, 1) to match Long, Long and avoid String, String - // despite not being an exact match in all arguments, - // since variables have an unknown type at runtime. - Iterator iterator = candidates.iterator(); - while (iterator.hasNext()) { - FunctionIdentifier candidate = iterator.next(); - int argIndex = 0; - - while (argIndex < provided.args.length) { - if (provided.args[argIndex] == Object.class) { - argIndex++; - continue; - } - - if (provided.args[argIndex] != candidate.args[argIndex]) { - iterator.remove(); - break; - } - - argIndex++; - } - } - - return Collections.unmodifiableSet(candidates); + return new Retrieval<>(RetrievalResult.valueOf(retrieval.result().name()), + (Signature) retrieval.retrieved(), + retrieval.conflictingArgs().stream() + .map(it -> it.toArray(Class[]::new)) + .toArray(Class[][]::new)); } /** - * Removes a function's signature from the registry. - * - * @param signature The signature to remove. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getSignatures(String, String)} instead. */ - public void remove(@NotNull Signature signature) { - Preconditions.checkNotNull(signature, "signature cannot be null"); - - String name = signature.getName(); - FunctionIdentifier identifier = FunctionIdentifier.of(signature); - - Namespace namespace; - if (signature.isLocal()) { - namespace = namespaces.get(new NamespaceIdentifier(signature.namespace())); - } else { - namespace = namespaces.get(GLOBAL_NAMESPACE); - } + @Deprecated(forRemoval = true, since = "INSERT VERSION") + public @Unmodifiable @NotNull Set> getSignatures(@Nullable String namespace, @NotNull String name) { + Set> signatures; if (namespace == null) { - return; - } - - for (FunctionIdentifier other : namespace.identifiers.getOrDefault(name, Set.of())) { - if (!identifier.equals(other)) { - continue; - } - - removeUpdateMaps(namespace, other, name); - return; - } - } - - /** - * Updates the maps by removing the provided function identifier from the maps. - * - * @param namespace The namespace - * @param toRemove The identifier to remove - * @param name The name of the function - */ - private void removeUpdateMaps(Namespace namespace, FunctionIdentifier toRemove, String name) { - namespace.identifiers.computeIfPresent(name, (k, set) -> { - if (set.remove(toRemove)) { - Skript.debug("Removed identifier '%s' from %s", toRemove, namespace); - } - return set.isEmpty() ? null : set; - }); - if (namespace.functions.remove(toRemove) != null) { - Skript.debug("Removed function '%s' from %s", toRemove, namespace); - } - if (namespace.signatures.remove(toRemove) != null) { - Skript.debug("Removed signature '%s' from %s", toRemove, namespace); - } - } - - /** - * An identifier for a function namespace. - */ - private record NamespaceIdentifier(@Nullable String name) { - - /** - * Returns whether this identifier is for local namespaces. - * - * @return Whether this identifier is for local namespaces. - */ - public boolean local() { - return name == null; + signatures = newRegistry.getSignatures(name); + } else { + signatures = newRegistry.getSignatures(namespace, name); } + return signatures.stream() + .map(it -> (Signature) it) + .collect(Collectors.toUnmodifiableSet()); } /** - * The data a namespace contains. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#remove(org.skriptlang.skript.common.function.Signature)} instead. */ - private static final class Namespace { - - /** - * Map for all function names to their identifiers, allowing for quicker lookup. - */ - private final Map> identifiers = new HashMap<>(); - - /** - * Map for all identifier to function combinations. - */ - private final Map> functions = new HashMap<>(); - - /** - * Map for all identifier to signature combinations. - */ - private final Map> signatures = new HashMap<>(); - - } - - /** - * An identifier for a function. - *

Used to differentiate between functions with the same name but different parameters.

- * - * @param name The name of the function. - * @param args The arguments of the function. - */ - record FunctionIdentifier(@NotNull String name, boolean local, int minArgCount, - @NotNull Class... args) { - - /** - * Returns the identifier for the given arguments. - * - * @param name The name of the function. - * @param args The types of the arguments. - * @return The identifier for the signature. - */ - static FunctionIdentifier of(@NotNull String name, boolean local, @NotNull Class... args) { - Preconditions.checkNotNull(name, "name cannot be null"); - Preconditions.checkNotNull(args, "args cannot be null"); - - return new FunctionIdentifier(name, local, args.length, args); - } - - /** - * Returns the identifier for the given signature. - * - * @param signature The signature to get the identifier for. - * @return The identifier for the signature. - */ - static FunctionIdentifier of(@NotNull Signature signature) { - Preconditions.checkNotNull(signature, "signature cannot be null"); - - Parameter[] signatureParams = signature.parameters().all(); - Class[] parameters = new Class[signatureParams.length]; - - int optionalArgs = 0; - for (int i = 0; i < signatureParams.length; i++) { - Parameter param = signatureParams[i]; - if (param.hasModifier(Modifier.OPTIONAL)) { - optionalArgs++; - } - - parameters[i] = param.type(); - } - - return new FunctionIdentifier(signature.getName(), signature.isLocal(), - parameters.length - optionalArgs, parameters); - } - - @Override - public int hashCode() { - return Objects.hash(name, Arrays.hashCode(args)); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof FunctionIdentifier other)) { - return false; - } - - if (!name.equals(other.name)) { - return false; - } - - if (args.length != other.args.length) { - return false; - } - - for (int i = 0; i < args.length; i++) { - if (args[i] != other.args[i]) { - return false; - } - } - - return true; - } - - @Override - public @NotNull String toString() { - return MoreObjects.toStringHelper(this) - .add("name", name) - .add("local", local) - .add("minArgCount", minArgCount) - .add("args", Arrays.stream(args).map(Class::getSimpleName).collect(Collectors.joining(", "))) - .toString(); - } - + @Deprecated(forRemoval = true, since = "INSERT VERSION") + public void remove(@NotNull Signature signature) { + newRegistry.remove(signature); } } diff --git a/src/main/java/ch/njol/skript/lang/function/Functions.java b/src/main/java/ch/njol/skript/lang/function/Functions.java index 6ce1da73e51..ba65e2fd1ce 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -6,29 +6,32 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; +import ch.njol.skript.structures.StructFunction; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.common.function.DefaultFunction; import org.skriptlang.skript.common.function.FunctionParser; import org.skriptlang.skript.common.function.FunctionReference; import org.skriptlang.skript.common.function.Parameter; +import org.skriptlang.skript.common.function.Signature.Modifier; import org.skriptlang.skript.lang.script.Script; import java.util.*; import java.util.stream.Collectors; /** - * Static methods to work with functions. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry} instead. */ +@Deprecated(forRemoval = true, since = "INSERT VERSION") public abstract class Functions { - private static final String INVALID_FUNCTION_DEFINITION = - "Invalid function definition. Please check for " + - "typos and make sure that the function's name " + - "only contains letters and underscores. " + - "Refer to the documentation for more information."; - - private Functions() {} + private Functions() { + } + /** + * @deprecated Unused. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable ScriptFunction currentFunction = null; /** @@ -51,14 +54,16 @@ private Functions() {} */ private static final Map globalFunctions = new HashMap<>(); + /** + * @deprecated Use {@link org.skriptlang.skript.common.function.Function#eventRegistry()} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static boolean callFunctionEvents = false; /** - * Registers a {@link DefaultFunction}. - * - * @param function The function to register. - * @return The registered function. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#register(org.skriptlang.skript.common.function.Function)} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static DefaultFunction register(DefaultFunction function) { Skript.checkAcceptRegistrations(); @@ -101,13 +106,9 @@ public static JavaFunction registerFunction(JavaFunction function) { public final static String functionNamePattern = "[\\p{IsAlphabetic}_][\\p{IsAlphabetic}\\d_]*"; /** - * Loads a script function from given node. - * @param script The script the function is declared in - * @param node Section node. - * @param signature The signature of the function. Use {@link Functions#parseSignature(String, String, String, String, boolean)} - * to get a new signature instance and {@link Functions#registerSignature(Signature)} to register the signature - * @return Script function, or null if something went wrong. + * @deprecated Handled by {@link StructFunction}. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Function loadFunction(Script script, SectionNode node, Signature signature) { String name = signature.getName(); Namespace namespace = getScriptNamespace(script.getConfig().getFileName()); @@ -117,10 +118,6 @@ public static JavaFunction registerFunction(JavaFunction function) { return null; // Probably duplicate signature; reported before } - if (Skript.debug() || node.debug()) { - Skript.debug(signature.toString()); - } - Function function; try { function = new ScriptFunction<>(signature, node); @@ -151,15 +148,24 @@ public static JavaFunction registerFunction(JavaFunction function) { */ @Deprecated(forRemoval = true, since = "2.14") public static @Nullable Signature parseSignature(String script, String name, String args, @Nullable String returnType, boolean local) { - return FunctionParser.parse(script, name, args, returnType, local); + return (Signature) FunctionParser.parse(script, name, args, returnType, local); + } + + @ApiStatus.Internal + public static void registerCompatibilitySignature(Signature signature) { + Namespace.Key namespaceKey = new Namespace.Key(Namespace.Origin.SCRIPT, signature.namespace()); + Namespace namespace = namespaces.computeIfAbsent(namespaceKey, k -> new Namespace()); + if (namespace.getSignature(signature.getName()) == null) { + namespace.addSignature(signature); + } + if (!signature.isLocal()) + globalFunctions.put(signature.getName(), namespace); } /** - * Registers the signature. - * @param signature The signature to register. - * @return Signature of function, or null if something went wrong. - * @see Functions#parseSignature(String, String, String, String, boolean) + * @deprecated Handled by {@link StructFunction}. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Signature registerSignature(Signature signature) { Retrieval> existing; Parameter[] parameters = signature.parameters().all(); @@ -177,10 +183,10 @@ public static JavaFunction registerFunction(JavaFunction function) { // if this function has already been registered, only allow it if one function is local and one is global. // if both are global or both are local, disallow. - if (existing.result() == RetrievalResult.EXACT && existing.retrieved().isLocal() == signature.isLocal()) { + if (existing.result() == RetrievalResult.EXACT && existing.retrieved().hasModifier(Modifier.Local.class) == signature.isLocal()) { StringBuilder error = new StringBuilder(); - if (existing.retrieved().isLocal()) { + if (existing.retrieved().hasModifier(Modifier.Local.class)) { error.append("Local function "); } else { error.append("Function "); @@ -217,27 +223,9 @@ public static JavaFunction registerFunction(JavaFunction function) { } /** - * Gets a function, if it exists. Note that even if function exists in scripts, - * it might not have been parsed yet. If you want to check for existence, - * then use {@link #getGlobalSignature(String)}. - * - * @deprecated in favour of {@link #getGlobalFunction(String)} for proper name. - * @param name Name of function. - * @return Function, or null if it does not exist. - */ - @Deprecated(since = "2.7.0", forRemoval = true) - public static @Nullable Function getFunction(String name) { - return getGlobalFunction(name); - } - - /** - * Gets a function, if it exists. Note that even if function exists in scripts, - * it might not have been parsed yet. If you want to check for existence, - * then use {@link #getGlobalSignature(String)}. - * - * @param name Name of function. - * @return Function, or null if it does not exist. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getFunction(String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Function getGlobalFunction(String name) { Namespace namespace = globalFunctions.get(name); if (namespace == null) @@ -246,14 +234,9 @@ public static JavaFunction registerFunction(JavaFunction function) { } /** - * Gets a function, if it exists. Note that even if function exists in scripts, - * it might not have been parsed yet. If you want to check for existence, - * then use {@link #getLocalSignature(String, String)}. - * - * @param name Name of function. - * @param script The script where the function is declared in. Used to get local functions. - * @return Function, or null if it does not exist. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getFunction(String, String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Function getLocalFunction(String name, String script) { Namespace namespace = null; Function function = null; @@ -264,15 +247,9 @@ public static JavaFunction registerFunction(JavaFunction function) { } /** - * Gets a local function, if it doesn't exist it'll fall back to a global function, - * if it exists. Note that even if function exists in scripts, - * it might not have been parsed yet. If you want to check for existence, - * then use {@link #getSignature(String, String)}. - * - * @param name Name of function. - * @param script The script where the function is declared in. Used to get local functions. - * @return Function, or null if it does not exist. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getFunction(String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Function getFunction(String name, @Nullable String script) { if (script == null) return getGlobalFunction(name); @@ -283,23 +260,9 @@ public static JavaFunction registerFunction(JavaFunction function) { } /** - * Gets a signature of function with given name. - * - * @deprecated in favour of {@link #getGlobalSignature(String)} for proper name. - * @param name Name of function. - * @return Signature, or null if function does not exist. - */ - @Deprecated(since = "2.7.0", forRemoval = true) - public static @Nullable Signature getSignature(String name) { - return getGlobalSignature(name); - } - - /** - * Gets a signature of function with given name. - * - * @param name Name of function. - * @return Signature, or null if function does not exist. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getSignature(String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Signature getGlobalSignature(String name) { Namespace namespace = globalFunctions.get(name); if (namespace == null) @@ -308,12 +271,9 @@ public static JavaFunction registerFunction(JavaFunction function) { } /** - * Gets a signature of function with given name. - * - * @param name Name of function. - * @param script The script where the function is declared in. Used to get local functions. - * @return Signature, or null if function does not exist. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getSignature(String, String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Signature getLocalSignature(String name, String script) { Namespace namespace = null; Signature signature = null; @@ -324,13 +284,9 @@ public static JavaFunction registerFunction(JavaFunction function) { } /** - * Gets a signature of local function with the given name, if no signature was found, - * it will fall back to a global function. - * - * @param name Name of function. - * @param script The script where the function is declared in. Used to get local functions. - * @return Signature, or null if function does not exist. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#getSignature(String, Class[])} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Signature getSignature(String name, @Nullable String script) { if (script == null) return getGlobalSignature(name); @@ -340,35 +296,20 @@ public static JavaFunction registerFunction(JavaFunction function) { return signature; } + /** + * @deprecated Pass script as a string instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static @Nullable Namespace getScriptNamespace(String script) { return namespaces.get(new Namespace.Key(Namespace.Origin.SCRIPT, script)); } private final static Collection> toValidate = new ArrayList<>(); - @Deprecated(since = "2.7.0", forRemoval = true) - public static int clearFunctions(String script) { - // Get and remove function namespace of script - Namespace namespace = namespaces.remove(new Namespace.Key(Namespace.Origin.SCRIPT, script)); - if (namespace == null) { // No functions defined - return 0; - } - - // Remove references to this namespace from global functions - globalFunctions.values().removeIf(loopedNamespaced -> loopedNamespaced == namespace); - - // Queue references to signatures we have for revalidation - // Can't validate here, because other scripts might be loaded soon - for (Signature sign : namespace.getSignatures()) { - for (FunctionReference ref : sign.calls()) { - if (!script.equals(ref.namespace())) { - toValidate.add(ref); - } - } - } - return namespace.getSignatures().size(); - } - + /** + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry#remove(org.skriptlang.skript.common.function.Signature)} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static void unregisterFunction(Signature signature) { FunctionRegistry.getRegistry().remove(signature); @@ -399,19 +340,6 @@ public static void validateFunctions() { toValidate.clear(); } - /** - * Clears all function calls and removes script functions. - */ - @Deprecated(since = "2.7.0", forRemoval = true) - public static void clearFunctions() { - // Keep Java functions, remove everything else - globalFunctions.values().removeIf(namespace -> namespace != javaNamespace); - namespaces.clear(); - - assert toValidate.isEmpty() : toValidate; - toValidate.clear(); - } - /** * @deprecated Use {@link #getFunctions()} instead. */ @@ -425,26 +353,18 @@ public static Collection> getJavaFunctions() { } /** - * Returns all functions registered using Java. - * - * @return All {@link JavaFunction} or {@link DefaultFunction} functions. + * @return Use {@link org.skriptlang.skript.common.function.FunctionRegistry#elements()} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static Collection> getFunctions() { return javaNamespace.getFunctions(); } + /** - * Normally, function calls do not cause actual Bukkit events to be - * called. If an addon requires such functionality, it should call this - * method. After doing so, the events will be called. Calling this method - * many times will not cause any additional changes. - *

- * Note that calling events is not free; performance might vary - * once you have enabled that. - * - * @param addon Addon instance. + * @deprecated Use {@link org.skriptlang.skript.common.function.Function#eventRegistry()} instead. */ - @SuppressWarnings({"null", "unused"}) + @Deprecated(forRemoval = true, since = "INSERT VERSION") public static void enableFunctionEvents(SkriptAddon addon) { if (addon == null) { throw new SkriptAPIException("enabling function events requires addon instance"); diff --git a/src/main/java/ch/njol/skript/lang/function/Namespace.java b/src/main/java/ch/njol/skript/lang/function/Namespace.java index 53ace1f0794..d971ee2aab7 100644 --- a/src/main/java/ch/njol/skript/lang/function/Namespace.java +++ b/src/main/java/ch/njol/skript/lang/function/Namespace.java @@ -1,6 +1,7 @@ package ch.njol.skript.lang.function; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.common.function.FunctionRegistry; import java.util.Collection; import java.util.HashMap; @@ -8,10 +9,11 @@ import java.util.Objects; /** - * Contains a set of functions. + * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionRegistry} instead. */ +@Deprecated(forRemoval = true, since = "INSERT VERSION") public class Namespace { - + /** * Origin of functions in namespace. */ @@ -20,18 +22,18 @@ public enum Origin { * Functions implemented in Java. */ JAVA, - + /** * Script functions. */ SCRIPT } - + /** * Key to a namespace. */ public static class Key { - + private final Origin origin; private final @Nullable String scriptName; @@ -41,7 +43,7 @@ public Key(Origin origin, @Nullable String scriptName) { this.origin = origin; this.scriptName = scriptName; } - + public Origin getOrigin() { return origin; } @@ -121,7 +123,7 @@ public boolean equals(Object o) { return getName().equals(info.getName()); } } - + /** * Signatures of known functions. */ @@ -160,28 +162,43 @@ public boolean removeSignature(Signature sign) { signatures.remove(info); return true; } - + @SuppressWarnings("null") public Collection> getSignatures() { return signatures.values(); } + /** + * @deprecated Use {@link FunctionRegistry#getFunction(String, String, Class[])} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public @Nullable Function getFunction(String name, boolean local) { return functions.get(new Info(name, local)); } + /** + * @deprecated Use {@link FunctionRegistry#getFunction(String, String, Class[])} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public @Nullable Function getFunction(String name) { Function function = getFunction(name, true); return function == null ? getFunction(name, false) : function; } + /** + * @deprecated Use {@link FunctionRegistry#register(org.skriptlang.skript.common.function.Function)} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public void addFunction(Function func) { Info info = new Info(func.getName(), func.getSignature().isLocal()); assert signatures.containsKey(info) : "missing signature for function"; functions.put(info, func); } - @SuppressWarnings("null") + /** + * @deprecated Use {@link FunctionRegistry#elements()} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") public Collection> getFunctions() { return functions.values(); } diff --git a/src/main/java/ch/njol/skript/lang/function/Parameter.java b/src/main/java/ch/njol/skript/lang/function/Parameter.java index 5b4a10fd461..e6d2d3da4fc 100644 --- a/src/main/java/ch/njol/skript/lang/function/Parameter.java +++ b/src/main/java/ch/njol/skript/lang/function/Parameter.java @@ -22,6 +22,7 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * @deprecated Use {@link ScriptParameter} @@ -88,10 +89,10 @@ public Parameter(String name, ClassInfo type, boolean single, @Nullable Expre this.modifiers = new HashSet<>(); if (def != null) { - modifiers.add(Modifier.OPTIONAL); + modifiers.add(new Modifier.Optional()); } if (keyed) { - modifiers.add(Modifier.KEYED); + modifiers.add(new Modifier.Keyed()); } } @@ -110,10 +111,10 @@ public Parameter(String name, ClassInfo type, boolean single, @Nullable Expre this.modifiers = new HashSet<>(); if (optional) { - modifiers.add(Modifier.OPTIONAL); + modifiers.add(new Modifier.Optional()); } if (keyed) { - modifiers.add(Modifier.KEYED); + modifiers.add(new Modifier.Keyed()); } } @@ -131,7 +132,7 @@ public Parameter(String name, ClassInfo type, boolean single, @Nullable Expre this.def = def; this.single = single; this.modifiers = Set.of(modifiers); - this.keyed = this.modifiers.contains(Modifier.KEYED); + this.keyed = hasModifier(Modifier.Keyed.class); } /** @@ -139,7 +140,7 @@ public Parameter(String name, ClassInfo type, boolean single, @Nullable Expre * @return Whether this parameter is optional or not. */ public boolean isOptional() { - return modifiers.contains(Modifier.OPTIONAL); + return hasModifier(Modifier.Optional.class); } /** @@ -180,10 +181,10 @@ public ClassInfo getType() { Set modifiers = new HashSet<>(); if (d != null) { - modifiers.add(Modifier.OPTIONAL); + modifiers.add(new Modifier.Optional()); } if (!single) { - modifiers.add(Modifier.KEYED); + modifiers.add(new Modifier.Keyed()); } return new Parameter<>(name, type, single, d, modifiers.toArray(new Modifier[0])); @@ -275,12 +276,11 @@ public boolean isSingleValue() { @Override public boolean equals(Object o) { - if (!(o instanceof Parameter parameter)) { - + if (!(o instanceof Parameter parameter)) return false; - } - return modifiers.equals(parameter.modifiers) + return modifiers.stream().map(Modifier::getClass).collect(Collectors.toSet()) + .equals(parameter.modifiers().stream().map(Modifier::getClass).collect(Collectors.toSet())) && single == parameter.single && name.equals(parameter.name) && type.equals(parameter.type) @@ -299,9 +299,9 @@ public String toString() { // ns: numbers between 0 and 100 = 3 public String toString(boolean debug) { String result = name + ": " + Utils.toEnglishPlural(type.getCodeName(), !single); - if (this.hasModifier(Modifier.RANGED)) { - RangedModifier range = this.getModifier(RangedModifier.class); - result += " between " + Classes.toString(range.getMin()) + " and " + Classes.toString(range.getMax()); + if (hasModifier(Modifier.Ranged.class)) { + Modifier.Ranged range = this.getModifier(Modifier.Ranged.class); + result += " between " + Classes.toString(range.min()) + " and " + Classes.toString(range.max()); } result += (def != null ? " = " + def.toString(null, debug) : ""); return result; @@ -315,7 +315,15 @@ public String toString(boolean debug) { @Override public @NotNull Class type() { //noinspection unchecked - return (Class) Signature.getReturns(single, type.getC()); + return (Class) getReturns(single, type.getC()); + } + + static Class getReturns(boolean single, Class cls) { + if (single) { + return cls; + } else { + return cls.arrayType(); + } } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java index 9bbba5cc8bb..2d1c1449989 100644 --- a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java @@ -7,7 +7,6 @@ import ch.njol.skript.util.Utils; import ch.njol.skript.variables.HintManager; import ch.njol.skript.variables.Variables; -import org.bukkit.event.Event; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.common.function.FunctionArguments; @@ -144,7 +143,7 @@ public boolean resetReturnValue() { } @Override - public final void returnValues(Event event, Expression value) { + public final void returnValues(org.bukkit.event.Event event, Expression value) { assert !returnValueSet.get(); returnValueSet.set(true); this.returnValues.set(value.getArray(event)); diff --git a/src/main/java/ch/njol/skript/lang/function/Signature.java b/src/main/java/ch/njol/skript/lang/function/Signature.java index 349ed2b25d0..272f46450f6 100644 --- a/src/main/java/ch/njol/skript/lang/function/Signature.java +++ b/src/main/java/ch/njol/skript/lang/function/Signature.java @@ -1,6 +1,5 @@ package ch.njol.skript.lang.function; -import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Contract; @@ -10,9 +9,10 @@ import com.google.common.collect.Lists; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; import org.skriptlang.skript.common.function.FunctionReference; -import org.skriptlang.skript.common.function.Parameter.Modifier; import org.skriptlang.skript.common.function.Parameters; +import org.skriptlang.skript.common.function.Signature.Modifier.Returns; import java.util.*; @@ -26,26 +26,14 @@ public class Signature implements org.skriptlang.skript.common.function.Signa */ final @Nullable String script; - /** - * Name of function this refers to. - */ - final String name; // Stored for hashCode - - /** - * Parameters taken by this function, in order. - */ + private final String name; private final Parameters parameters; - - /** - * Whether this function is only accessible in the script it was declared in - */ - final boolean local; + private final Set modifiers = new HashSet<>(); /** * The return type. */ final @Nullable ClassInfo returnType; - final Class returns; /** * Whether this function returns a single value, or multiple ones. @@ -64,52 +52,43 @@ public class Signature implements org.skriptlang.skript.common.function.Signa final @Nullable Contract contract; /** - * The class path for the origin of this signature. + * @deprecated Use {@link Signature#Signature(String, Set, SequencedMap, Contract)} instead. */ - @Nullable String originClassPath; - - static Class getReturns(boolean single, Class cls) { - if (single) { - return cls; - } else { - return cls.arrayType(); - } - } - + @Deprecated(since = "INSERT VERSION", forRemoval = true) public Signature(@Nullable String script, String name, Parameter[] parameters, boolean local, @Nullable ClassInfo returnType, boolean single, @Nullable Contract contract) { this.script = script; this.name = name; this.parameters = initParameters(parameters); - this.local = local; + if (local) + modifiers.add(new Modifier.Local(script)); this.returnType = returnType; this.single = single; - if (returnType == null) { - this.returns = null; - } else { - this.returns = getReturns(single, returnType.getC()); - } this.contract = contract; this.calls = Collections.newSetFromMap(new WeakHashMap<>()); - this.originClassPath = ""; } + /** + * @deprecated Use {@link Signature#Signature(String, Set, SequencedMap, Contract)} instead. + */ + @Deprecated(since = "INSERT VERSION", forRemoval = true) public Signature(@Nullable String script, String name, Parameter[] parameters, boolean local, @Nullable ClassInfo returnType, boolean single, String stacktrace) { this(script, name, parameters, local, returnType, single, (Contract) null); - this.originClassPath = stacktrace; } + /** + * @deprecated Use {@link Signature#Signature(String, Set, SequencedMap, Contract)} instead. + */ + @Deprecated(since = "INSERT VERSION", forRemoval = true) public Signature(String script, String name, Parameter[] parameters, boolean local, ClassInfo returnType, boolean single, String stacktrace, @Nullable Contract contract) { this(script, name, parameters, local, returnType, single, contract); - - this.originClassPath = stacktrace; } public Signature(@Nullable String script, String name, Parameters parameters, Class returnType, boolean local, @Nullable Contract contract) { this.script = script; this.name = name; this.parameters = parameters; - this.local = local; - this.returns = returnType; + if (local) + modifiers.add(new Modifier.Local(script)); if (returnType != null) { //noinspection unchecked this.returnType = (ClassInfo) Classes.getExactClassInfo(Utils.getComponentType(returnType)); @@ -130,6 +109,37 @@ public Signature(String namespace, String name, org.skriptlang.skript.common.fun this(namespace, name, initParameters(parameters), returnType, false, contract); } + public Signature(String name, Set modifiers, SequencedMap> parameters, @Nullable Contract contract) { + this.modifiers.addAll(modifiers); + + this.name = name; + this.parameters = new Parameters(parameters); + + if (hasModifier(Modifier.Local.class)) { + this.script = getModifier(Modifier.Local.class).namespace(); + } else { + this.script = null; + } + + if (hasModifier(Modifier.Returns.class)) { + Class type = getModifier(Returns.class).type(); + //noinspection unchecked + this.returnType = (ClassInfo) Classes.getExactClassInfo(Utils.getComponentType(type)); + this.single = !type.isArray(); + } else { + this.returnType = null; + this.single = true; + } + + this.contract = contract; + this.calls = Collections.newSetFromMap(new WeakHashMap<>()); + } + + @Override + public @NotNull String name() { + return name; + } + private static Parameters initParameters(org.skriptlang.skript.common.function.Parameter[] params) { SequencedMap> map = new LinkedHashMap<>(); for (org.skriptlang.skript.common.function.Parameter parameter : params) { @@ -140,6 +150,7 @@ private static Parameters initParameters(org.skriptlang.skript.common.function.P /** * Converts a {@link org.skriptlang.skript.common.function.Parameter} to a {@link Parameter}. + * * @param parameter The parameter to use to convert. * @return The converted parameter. */ @@ -150,7 +161,7 @@ static Parameter toOldParameter(org.skriptlang.skript.common.function.Paramet ClassInfo classInfo = Classes.getExactClassInfo(Utils.getComponentType(parameter.type())); return new Parameter<>(parameter.name(), classInfo, !parameter.type().isArray(), null, - parameter.modifiers().toArray(new Modifier[0])); + parameter.modifiers().toArray(new org.skriptlang.skript.common.function.Parameter.Modifier[0])); } /** @@ -171,15 +182,6 @@ public Parameter[] getParameters() { .toArray(Parameter[]::new); } - @Override - public @Nullable Class returnType() { - //noinspection unchecked - return (Class) returns; - } - - /** - * @return A {@link SequencedMap} containing all parameters. - */ @Override public @NotNull Parameters parameters() { return parameters; @@ -210,12 +212,13 @@ public String getName() { } public boolean isLocal() { - return local; + return hasModifier(Modifier.Local.class); } /** * @return The namespace of this signature. */ + @Override public String namespace() { return script; } @@ -232,12 +235,9 @@ public boolean isSingle() { return single; } - /** - * @deprecated Unused. - */ - @Deprecated(forRemoval = true, since = "2.13") - public String getOriginClassPath() { - return originClassPath; + @Override + public @Unmodifiable @NotNull Set modifiers() { + return Collections.unmodifiableSet(modifiers); } public @Nullable Contract getContract() { @@ -270,7 +270,7 @@ public int getMinParameters() { int i = parameters.size() - 1; for (org.skriptlang.skript.common.function.Parameter parameter : Lists.reverse(params)) { - if (!parameter.hasModifier(Modifier.OPTIONAL)) { + if (!parameter.hasModifier(org.skriptlang.skript.common.function.Parameter.Modifier.Optional.class)) { return i + 1; } i--; @@ -286,24 +286,25 @@ public int hashCode() { @Override public String toString() { - return toString(true, Skript.debug()); + return toFormattedString(); } public String toString(boolean includeReturnType, boolean debug) { StringBuilder signatureBuilder = new StringBuilder(); - if (local) + if (isLocal()) signatureBuilder.append("local "); + signatureBuilder.append(name); signatureBuilder.append('(') .append(StringUtils.join(parameters.all(), ", ")) .append(')'); - if (includeReturnType && returns != null) { - signatureBuilder.append(" :: "); + if (includeReturnType && hasModifier(Modifier.Returns.class)) { + signatureBuilder.append(" returns "); - signatureBuilder.append(Utils.toEnglishPlural(returnType.getCodeName(), returns.isArray())); + signatureBuilder.append(Utils.toEnglishPlural(returnType.getCodeName(), getModifier(Modifier.Returns.class).type().isArray())); } return signatureBuilder.toString(); diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index 0e3a3740e46..5d84161d69a 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -2,60 +2,64 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; -import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.SkriptAPIException; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.ParseContext; -import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.function.FunctionEvent; import ch.njol.skript.lang.function.Functions; -import ch.njol.skript.lang.function.Signature; +import ch.njol.skript.lang.function.ScriptFunction; import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.Utils.PluralResult; -import ch.njol.util.StringUtils; import org.bukkit.event.Event; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.common.function.*; +import org.skriptlang.skript.common.function.Function; +import org.skriptlang.skript.common.function.FunctionParser; +import org.skriptlang.skript.common.function.FunctionRegistry.Retrieval; +import org.skriptlang.skript.common.function.FunctionRegistry.RetrievalResult; +import org.skriptlang.skript.common.function.Parameter; +import org.skriptlang.skript.common.function.Signature; +import org.skriptlang.skript.common.function.Signature.Modifier; import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.SequencedMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @Name("Function") @Description({ - "Functions are structures that can be executed with arguments/parameters to run code.", - "They can also return a value to the trigger that is executing the function.", - "Note that local functions come before global functions execution" + "Functions are structures that can be executed with arguments/parameters to run code.", + "They can also return a value to the trigger that is executing the function.", + "Note that local functions come before global functions execution" }) @Example(""" - function sayMessage(message: text): - broadcast {_message} # our message argument is available in '{_message}' - """) + function sayMessage(message: text): + broadcast {_message} # our message argument is available in '{_message}' + """) @Example(""" - local function giveApple(amount: number) :: item: - return {_amount} of apple - """) + local function giveApple(amount: number) :: item: + return {_amount} of apple + """) @Example(""" - function getPoints(p: player) returns number: - return {points::%{_p}%} - """) + function getPoints(p: player) returns number: + return {points::%{_p}%} + """) @Since("2.2, 2.7 (local functions)") public class StructFunction extends Structure { + private static org.skriptlang.skript.common.function.FunctionRegistry registry; + + @ApiStatus.Internal + public static void setRegistry(org.skriptlang.skript.common.function.FunctionRegistry registry) { + StructFunction.registry = registry; + } + public static final Priority PRIORITY = new Priority(400); /** @@ -81,12 +85,12 @@ public class StructFunction extends Structure { *

*/ private static final Pattern SIGNATURE_PATTERN = - Pattern.compile("^(?:local )?function (?" + Functions.functionNamePattern + ")\\((?.*?)\\)(?:\\s*(?:->|::| returns )\\s*(?.+))?$"); + Pattern.compile("^(?:local )?function (?[\\p{IsAlphabetic}_][\\p{IsAlphabetic}\\d_]*)\\((?.*?)\\)(?:\\s*(?:->|::| returns )\\s*(?.+))?$"); private static final AtomicBoolean VALIDATE_FUNCTIONS = new AtomicBoolean(); static { Skript.registerStructure(StructFunction.class, - "[:local] function <.+>" + "[:local] function <.+>" ); } @@ -119,13 +123,71 @@ public boolean preLoad() { // parse signature getParser().setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); signature = FunctionParser.parse( - getParser().getCurrentScript().getConfig().getFileName(), - matcher.group("name"), matcher.group("args"), matcher.group("returns"), local + getParser().getCurrentScript().getConfig().getFileName(), + matcher.group("name"), matcher.group("args"), matcher.group("returns"), local ); getParser().deleteCurrentEvent(); // attempt registration - return signature != null && Functions.registerSignature(signature) != null; + return signature != null && registerSignature(signature) != null; + } + + private static Signature registerSignature(Signature signature) { + Retrieval> existing; + Parameter[] parameters = signature.parameters().all(); + if (parameters.length == 1 && !parameters[0].isSingle()) { + if (signature.hasModifier(Modifier.Local.class)) { + existing = registry.getExactSignature(signature.namespace(), signature.name(), parameters[0].type().arrayType()); + } else { + existing = registry.getExactSignature(signature.name()); + } + } else { + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + types[i] = parameters[i].type(); + } + + if (signature.hasModifier(Modifier.Local.class)) { + existing = registry.getExactSignature(signature.namespace(), signature.name(), types); + } else { + existing = registry.getExactSignature(signature.name(), types); + } + } + + // if this function has already been registered, only allow it if one function is local and one is global. + // if both are global or both are local, disallow. + if (existing.result() == RetrievalResult.EXACT && existing.retrieved().hasModifier(Modifier.Local.class) == signature.hasModifier(Modifier.Local.class)) { + StringBuilder error = new StringBuilder(); + + if (existing.retrieved().hasModifier(Modifier.Local.class)) { + error.append("Local function "); + } else { + error.append("Function "); + } + error.append("'%s' with the same argument types already exists".formatted(signature.name())); + if (existing.retrieved().namespace() != null) { + error.append(" in script '%s'.".formatted(existing.retrieved().namespace())); + } else { + error.append("."); + } + + Skript.error(error.toString()); + + return null; + } + + if (signature instanceof ch.njol.skript.lang.function.Signature oldSignature) + Functions.registerCompatibilitySignature(oldSignature); + + if (signature.hasModifier(Modifier.Local.class)) { + registry.register(signature.namespace(), signature); + } else { + registry.register(signature); + } + + Skript.debug("Registered function signature: " + signature.name()); + + return signature; } @Override @@ -135,7 +197,7 @@ public boolean load() { assert signature != null; // noinspection ConstantConditions - entry container cannot be null as this structure is not simple - Functions.loadFunction(parser.getCurrentScript(), source, signature); + loadFunction(parser.getCurrentScript(), source, signature); parser.deleteCurrentEvent(); @@ -144,6 +206,26 @@ public boolean load() { return true; } + private static void loadFunction(Script script, SectionNode node, Signature signature) { + Function function; + try { + function = new ScriptFunction<>((ch.njol.skript.lang.function.Signature) signature, node); + } catch (SkriptAPIException ex) { + //noinspection ThrowableNotThrown + Skript.exception(ex, "Error while trying to load a function"); + + // avoid getting a "function is already registered" error when the function implementation is not known yet + registry.remove(signature); + return; + } + + if (function.signature().hasModifier(Modifier.Local.class)) { + registry.register(script.getConfig().getFileName(), function); + } else { + registry.register(function); + } + } + @Override public boolean postLoad() { if (VALIDATE_FUNCTIONS.get()) { @@ -156,8 +238,8 @@ public boolean postLoad() { @Override public void unload() { assert signature != null; - Functions.unregisterFunction(signature); - signature.calls().forEach(FunctionReference::invalidate); + registry.remove(signature); +// signature.calls().forEach(FunctionReference::invalidate); VALIDATE_FUNCTIONS.set(true); } diff --git a/src/main/java/ch/njol/skript/util/PaperUtils.java b/src/main/java/ch/njol/skript/util/PaperUtils.java index 6f312a9c50e..2106fed72d0 100644 --- a/src/main/java/ch/njol/skript/util/PaperUtils.java +++ b/src/main/java/ch/njol/skript/util/PaperUtils.java @@ -13,9 +13,9 @@ public class PaperUtils { private static final boolean REGISTRY_KEY_EXISTS = Skript.classExists("io.papermc.paper.registry.RegistryKey"); /** - * Check if a registry exists within {@link RegistryKey} - * @param registry Registry to check for (Fully qualified name of registry) - * @return True if registry exists else false + * Check if a registry exists within {@link RegistryKey}. + * @param registry Registry to check for. + * @return True if registry exists else false. */ public static boolean registryExists(String registry) { return REGISTRY_ACCESS_EXISTS @@ -24,21 +24,34 @@ public static boolean registryExists(String registry) { } /** - * Gets the Bukkit {@link Registry} from Paper's {@link RegistryKey}. - * @param registry Registry to get (Fully qualified name of registry). + * Gets a Paper {@link RegistryKey}. + * @param registry Registry key to get. * @return The Bukkit {@link Registry} if registry exists else {@code null}. */ - public static @Nullable Registry getBukkitRegistry(String registry) { + public static @Nullable RegistryKey getBukkitRegistryKey(String registry) { if (!registryExists(registry)) return null; - RegistryKey registryKey; + RegistryKey registryKey; try { - registryKey = (RegistryKey) RegistryKey.class.getField(registry).get(null); + //noinspection unchecked + registryKey = (RegistryKey) RegistryKey.class.getField(registry).get(null); } catch (NoSuchFieldException | IllegalAccessException ignored) { return null; } - //noinspection unchecked - return (Registry) RegistryAccess.registryAccess().getRegistry(registryKey); + return registryKey; + } + + /** + * Gets the Bukkit {@link Registry} from Paper's {@link RegistryKey}. + * @param registry Registry to get. + * @return The Bukkit {@link Registry} if registry exists else {@code null}. + */ + public static @Nullable Registry getBukkitRegistry(String registry) { + RegistryKey registryKey = getBukkitRegistryKey(registry); + if (registryKey == null) { + return null; + } + return RegistryAccess.registryAccess().getRegistry(registryKey); } } diff --git a/src/main/java/ch/njol/skript/util/Patterns.java b/src/main/java/ch/njol/skript/util/Patterns.java index 5f2b9061a29..486e911d77c 100644 --- a/src/main/java/ch/njol/skript/util/Patterns.java +++ b/src/main/java/ch/njol/skript/util/Patterns.java @@ -40,6 +40,24 @@ public Patterns(Object[][] info) { } } + /** + * Creates a new {@link Patterns} with the provided paired inputs. + * @param info Pattern-Value pairs, for example: + * + * new Patterns("pattern1", value1, "pattern2", value2); + * + */ + public Patterns(Object... info) { + patterns = new String[info.length / 2]; + types = new Object[info.length / 2]; + for (int i = 0; i < info.length; i += 2) { + int pattern = i / 2; + patterns[pattern] = (String) info[i]; + types[pattern] = info[i + 1]; + matchedPatterns.computeIfAbsent(info[i + 1], list -> new ArrayList<>()).add(pattern); + } + } + /** * Returns an array of the registered patterns. * @return An {@link java.lang.reflect.Array} of {@link String}s. diff --git a/src/main/java/org/skriptlang/skript/bukkit/damagesource/DamageSourceModule.java b/src/main/java/org/skriptlang/skript/bukkit/damagesource/DamageSourceModule.java index f3ea90c5eaf..aaaeaf03f4c 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/damagesource/DamageSourceModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/damagesource/DamageSourceModule.java @@ -43,7 +43,7 @@ protected void initSelf(SkriptAddon addon) { ); Classes.registerClass( - new RegistryClassInfo<>(DamageType.class, RegistryAccess.registryAccess().getRegistry(RegistryKey.DAMAGE_TYPE), + new RegistryClassInfo<>(DamageType.class, RegistryKey.DAMAGE_TYPE, "damagetype", "damage types") .user("damage ?types?") .name("Damage Type") diff --git a/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentClassInfo.java index c5b83769200..57d8eb83f71 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/enchantments/types/EnchantmentClassInfo.java @@ -1,7 +1,6 @@ package org.skriptlang.skript.bukkit.enchantments.types; import ch.njol.skript.classes.registry.RegistryClassInfo; -import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import org.bukkit.enchantments.Enchantment; import org.jetbrains.annotations.ApiStatus; @@ -10,7 +9,7 @@ public class EnchantmentClassInfo extends RegistryClassInfo { public EnchantmentClassInfo() { - super(Enchantment.class, RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT), + super(Enchantment.class, RegistryKey.ENCHANTMENT, "enchantment", "enchantments"); user("enchantments?") diff --git a/src/main/java/org/skriptlang/skript/bukkit/entity/entitydata/ZombieNautilusData.java b/src/main/java/org/skriptlang/skript/bukkit/entity/entitydata/ZombieNautilusData.java index 8084009f35b..ed6eadc59e0 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/entity/entitydata/ZombieNautilusData.java +++ b/src/main/java/org/skriptlang/skript/bukkit/entity/entitydata/ZombieNautilusData.java @@ -8,6 +8,7 @@ import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import com.google.common.collect.Iterators; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import org.bukkit.Registry; @@ -26,14 +27,14 @@ public static void register() { EntityData.register(ZombieNautilusData.class, "zombie nautilus", ZombieNautilus.class, 0, "zombie nautilus"); Variables.yggdrasil.registerSingleClass(Variant.class, "ZombieNautilus.Variant"); - Registry<@NotNull Variant> variantRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.ZOMBIE_NAUTILUS_VARIANT); - VARIANTS = variantRegistry.stream().toArray(Variant[]::new); - Classes.registerClass(new RegistryClassInfo<>(Variant.class, variantRegistry, "zombienautilusvariant", "zombie nautilus variants") + var zombieNautilusVariantInfo = new RegistryClassInfo<>(Variant.class, RegistryKey.ZOMBIE_NAUTILUS_VARIANT, "zombienautilusvariant", "zombie nautilus variants"); + Classes.registerClass(zombieNautilusVariantInfo .user("zombie ?nautilus ?variants?") .name("Zombie Nautilus Variant") .description("Represents the variant of a zombie nautilus.") .since("2.14") .documentationId("ZombieNautilusVariant")); + VARIANTS = Iterators.toArray(zombieNautilusVariantInfo.getSupplier().get(), Variant.class); } private Kleenean isTamed = Kleenean.UNKNOWN; diff --git a/src/main/java/org/skriptlang/skript/bukkit/item/ItemModule.java b/src/main/java/org/skriptlang/skript/bukkit/item/ItemModule.java index d3b5d311cda..759f7a4b236 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/item/ItemModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/item/ItemModule.java @@ -24,7 +24,9 @@ public Iterable children() { @Override public void loadSelf(SkriptAddon addon) { register(addon, + ExprItemModel::register, ExprItemWithLore::register, + ExprItemWithModel::register, ExprLore::register ); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/item/elements/ExprItemModel.java b/src/main/java/org/skriptlang/skript/bukkit/item/elements/ExprItemModel.java new file mode 100644 index 00000000000..45a69823aa6 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/item/elements/ExprItemModel.java @@ -0,0 +1,82 @@ +package org.skriptlang.skript.bukkit.item.elements; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.NamespacedUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Item Model") +@Description(""" + The item model of an item. \ + Accepts a Namespaced Key which takes the form of "namespace:key", e.g. "minecraft:dirt". \ + See this article for more detail. \ + """) +@Example("set the item model of player's held item to \"diamond\"") +@Example("set the item model of {_item} to \"minecraft:dirt\"") +@Since("INSERT VERSION") +public class ExprItemModel extends SimplePropertyExpression { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder(ExprItemModel.class, String.class, "item model", "itemtypes", true) + .supplier(ExprItemModel::new) + .build() + ); + } + + @Override + public @Nullable String convert(ItemType from) { + NamespacedKey key = from.getItemMeta().getItemModel(); + if (key == null) + return null; + + return key.asString(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(String.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + NamespacedKey key = null; + if (delta != null) { + key = NamespacedUtils.checkValidationAndSend((String) delta[0], this); + if (key == null) { + return; + } + } + + for (ItemType item : getExpr().getArray(event)) { + ItemMeta meta = item.getItemMeta(); + meta.setItemModel(key); + item.setItemMeta(meta); + } + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + protected String getPropertyName() { + return "item model"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/item/elements/ExprItemWithModel.java b/src/main/java/org/skriptlang/skript/bukkit/item/elements/ExprItemWithModel.java new file mode 100644 index 00000000000..2e390b46d02 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/item/elements/ExprItemWithModel.java @@ -0,0 +1,80 @@ +package org.skriptlang.skript.bukkit.item.elements; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.NamespacedUtils; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.DefaultSyntaxInfos; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Item With Model") +@Description(""" + Returns a copy of an item with a custom item model. \ + Accepts a Namespaced Key which takes the form of "namespace:key", e.g. "minecraft:dirt". \ + See this article for more detail. \ + """) +@Example(""" + set {_item} to emerald with model "minecraft:diamond" named "fake diamond"' + give {_item} to player + """) +@Since("INSERT VERSION") +@Keywords("item model") +public class ExprItemWithModel extends PropertyExpression { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register( + SyntaxRegistry.EXPRESSION, + DefaultSyntaxInfos.Expression.builder(ExprItemWithModel.class, ItemType.class) + .addPattern("%itemtype% with [the] [item] model %string%") + .supplier(ExprItemWithModel::new) + .build() + ); + } + + private Expression key; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression) exprs[0]); + key = (Expression) exprs[1]; + return true; + } + + @Override + protected ItemType[] get(Event event, ItemType[] source) { + String key = this.key.getSingle(event); + if (key == null) + return source; + + NamespacedKey namespacedKey = NamespacedUtils.checkValidationAndSend(key, this); + if (namespacedKey == null) + return source; + + return get(source, itemType -> { + itemType = itemType.clone(); + ItemMeta itemMeta = itemType.getItemMeta(); + itemMeta.setItemModel(namespacedKey); + itemType.setItemMeta(itemMeta); + return itemType; + }); + } + + @Override + public Class getReturnType() { + return ItemType.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return getExpr().toString(event, debug) + " with item model " + key.toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java b/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java index e783e25c460..8acb847038f 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/potion/PotionModule.java @@ -1,19 +1,17 @@ package org.skriptlang.skript.bukkit.potion; import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.BukkitUtils; import ch.njol.skript.classes.*; import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.registrations.Classes; import ch.njol.yggdrasil.Fields; -import org.bukkit.Registry; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.event.entity.EntityPotionEffectEvent; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectTypeCategory; -import org.jetbrains.annotations.NotNull; import org.skriptlang.skript.addon.AddonModule; import org.skriptlang.skript.addon.HierarchicalAddonModule; import org.skriptlang.skript.addon.SkriptAddon; @@ -130,15 +128,7 @@ protected boolean canBeInstantiated() { } })); - Registry<@NotNull PotionEffectType> petRegistry; - if (BukkitUtils.registryExists("MOB_EFFECT")) { // Paper (1.21.4) - petRegistry = Registry.MOB_EFFECT; - } else if (BukkitUtils.registryExists("EFFECT")) { // Bukkit (1.20.3) - petRegistry = Registry.EFFECT; - } else { - throw new IllegalStateException("Potion effect registry does not exist"); - } - Classes.registerClass(new RegistryClassInfo<>(PotionEffectType.class, petRegistry, "potioneffecttype", "potion effect types") + Classes.registerClass(new RegistryClassInfo<>(PotionEffectType.class, RegistryKey.MOB_EFFECT, "potioneffecttype", "potion effect types") .user("potion ?effect ?types?") .name("Potion Effect Type") .description("A potion effect type, e.g. 'strength' or 'swiftness'.") diff --git a/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java b/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java index ea806f86a0d..958dd211f73 100644 --- a/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java +++ b/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java @@ -6,6 +6,8 @@ import org.skriptlang.skript.addon.SkriptAddon; import org.skriptlang.skript.common.function.Parameter.Modifier; +import java.util.function.Consumer; + /** * A function that has been implemented in Java, instead of in Skript. *

@@ -39,6 +41,7 @@ public sealed interface DefaultFunction /** * Creates a new builder for a function. * + * @param source The addon registering this function. * @param name The name of the function. * @param returnType The type of the function. * @param The return type. @@ -49,6 +52,18 @@ public sealed interface DefaultFunction return new DefaultFunctionImpl.BuilderImpl<>(source, name, returnType); } + /** + * Creates a new builder for a function. + * + * @param source The addon registering this function. + * @param name The name of the function. + * @return The builder for a function. + */ + @Contract("_, _ -> new") + static @NotNull VoidBuilder builder(@NotNull SkriptAddon source, @NotNull String name) { + return new DefaultFunctionImpl.VoidBuilderImpl<>(source, name); + } + /** * @return The addon this function was registered for. */ @@ -61,6 +76,15 @@ public sealed interface DefaultFunction */ interface Builder { + /** + * Adds modifiers to this builder. + * + * @param modifiers The modifiers for this function. + * @return This builder. + */ + @Contract("_ -> this") + Builder modifiers(@NotNull Signature.Modifier @NotNull ... modifiers); + /** * Sets this function builder's {@link ch.njol.skript.util.Contract}. * @@ -136,4 +160,95 @@ interface Builder { } + /** + * Represents a builder for {@link DefaultFunction DefaultFunctions} with no return value. + * + * @param The return type of the function. + */ + interface VoidBuilder { + + /** + * Adds modifiers to this builder. + * + * @param modifiers The modifiers for this function. + * @return This builder. + */ + @Contract("_ -> this") + VoidBuilder modifiers(@NotNull Signature.Modifier @NotNull ... modifiers); + + /** + * Sets this function builder's {@link ch.njol.skript.util.Contract}. + * + * @param contract The contract. + * @return This builder. + */ + @Contract("_ -> this") + VoidBuilder contract(@NotNull ch.njol.skript.util.Contract contract); + + /** + * Sets this function builder's description. + * + * @param description The description. + * @return This builder. + */ + @Contract("_ -> this") + VoidBuilder description(@NotNull String @NotNull ... description); + + /** + * Sets this function builder's version history. + * + * @param since The version information. + * @return This builder. + */ + @Contract("_ -> this") + VoidBuilder since(@NotNull String @NotNull ... since); + + /** + * Sets this function builder's examples. + * + * @param examples The examples. + * @return This builder. + */ + @Contract("_ -> this") + VoidBuilder examples(@NotNull String @NotNull ... examples); + + /** + * Sets this function builder's keywords. + * + * @param keywords The keywords. + * @return This builder. + */ + @Contract("_ -> this") + VoidBuilder keywords(@NotNull String @NotNull ... keywords); + + /** + * Sets this function builder's requires. + * + * @param requires The requirements. + * @return This builder. + */ + @Contract("_ -> this") + VoidBuilder requires(@NotNull String @NotNull ... requires); + + /** + * Adds a parameter to this function builder. + * + * @param name The parameter name. + * @param type The type of the parameter. + * @param modifiers The {@link Modifier}s to apply to this parameter. + * @return This builder. + */ + @Contract("_, _, _ -> this") + VoidBuilder parameter(@NotNull String name, @NotNull Class type, Modifier @NotNull ... modifiers); + + /** + * Completes this builder with the code to execute on call of this function. + * + * @param execute The code to execute. + * @return The final function. + */ + DefaultFunction build(@NotNull Consumer execute); + + } + } diff --git a/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java b/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java index ff315999c30..9c73095dada 100644 --- a/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java +++ b/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java @@ -1,17 +1,16 @@ package org.skriptlang.skript.common.function; import ch.njol.skript.lang.function.FunctionEvent; -import ch.njol.skript.lang.function.Signature; import com.google.common.base.Preconditions; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; import org.skriptlang.skript.addon.SkriptAddon; -import org.skriptlang.skript.common.function.Parameter.Modifier; import org.skriptlang.skript.common.function.Parameter.Modifier.RangedModifier; import java.lang.reflect.Array; import java.util.*; +import java.util.function.Consumer; import java.util.function.Function; final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function implements DefaultFunction { @@ -29,19 +28,18 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function DefaultFunctionImpl( SkriptAddon source, String name, + Set modifiers, SequencedMap> parameters, - Class returnType, boolean single, @Nullable ch.njol.skript.util.Contract contract, Function execute, String[] description, String[] since, String[] examples, String[] keywords, String[] requires ) { - super(new Signature<>(null, name, parameters.values().toArray(new Parameter[0]), returnType, single, contract)); + super(new ch.njol.skript.lang.function.Signature<>(name, modifiers, parameters, contract)); Preconditions.checkNotNull(source, "source cannot be null"); Preconditions.checkNotNull(name, "name cannot be null"); Preconditions.checkNotNull(parameters, "parameters cannot be null"); - Preconditions.checkNotNull(returnType, "return type cannot be null"); Preconditions.checkNotNull(execute, "execute cannot be null"); this.source = source; @@ -65,15 +63,15 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function Parameter parameter = arrayParams[i]; if (arg == null || arg.length == 0) { - if (parameter.hasModifier(Modifier.OPTIONAL)) { + if (parameter.hasModifier(Parameter.Modifier.Optional.class)) { continue; } else { return null; } } - if (parameter.hasModifier(Modifier.RANGED)) { - RangedModifier range = parameter.getModifier(RangedModifier.class); + if (parameter.hasModifier(Parameter.Modifier.Ranged.class)) { + Parameter.Modifier.Ranged range = parameter.getModifier(Parameter.Modifier.Ranged.class); if (!range.inRange(arg)) { return null; } @@ -121,12 +119,12 @@ public T execute(@NotNull FunctionEvent event, @NotNull FunctionArguments arg Parameter parameter = parameters.get(name); Object value = arguments.get(name); - if (value == null && !parameter.hasModifier(Modifier.OPTIONAL)) { + if (value == null && !parameter.hasModifier(Parameter.Modifier.Optional.class)) { return null; } - if (parameter.hasModifier(Modifier.RANGED)) { - RangedModifier range = parameter.getModifier(RangedModifier.class); + if (parameter.hasModifier(Parameter.Modifier.Ranged.class)) { + Parameter.Modifier.Ranged range = parameter.getModifier(Parameter.Modifier.Ranged.class); if (!range.inRange(value)) { return null; } @@ -177,7 +175,7 @@ public boolean resetReturnValue() { } @Override - public org.skriptlang.skript.common.function.@NotNull Signature signature() { + public @NotNull Signature signature() { return getSignature(); } @@ -185,8 +183,8 @@ static class BuilderImpl implements DefaultFunctionImpl.Builder { private final SkriptAddon source; private final String name; - private final Class returnType; private final SequencedMap> parameters = new LinkedHashMap<>(); + private final Set modifiers = new HashSet<>(); private ch.njol.skript.util.Contract contract = null; @@ -203,7 +201,16 @@ static class BuilderImpl implements DefaultFunctionImpl.Builder { this.source = source; this.name = name; - this.returnType = returnType; + this.modifiers.add(new Signature.Modifier.Returns<>(returnType)); + } + + @Override + public Builder modifiers(Signature.Modifier @NotNull ... modifiers) { + Preconditions.checkNotNull(modifiers, "modifiers cannot be null"); + checkNotNull(modifiers, "modifiers contents cannot be null"); + + this.modifiers.addAll(Set.of(modifiers)); + return this; } @Override @@ -260,7 +267,7 @@ public Builder requires(@NotNull String @NotNull ... requires) { } @Override - public Builder parameter(@NotNull String name, @NotNull Class type, Modifier @NotNull ... modifiers) { + public Builder parameter(@NotNull String name, @NotNull Class type, Parameter.Modifier @NotNull ... modifiers) { Preconditions.checkNotNull(name, "name cannot be null"); Preconditions.checkNotNull(type, "type cannot be null"); @@ -272,20 +279,117 @@ public Builder parameter(@NotNull String name, @NotNull Class type, Modifi public DefaultFunction build(@NotNull Function execute) { Preconditions.checkNotNull(execute, "execute cannot be null"); - return new DefaultFunctionImpl<>(source, name, parameters, - returnType, !returnType.isArray(), contract, execute, + return new DefaultFunctionImpl<>(source, name, modifiers, parameters, + contract, execute, description, since, examples, keywords, requires); } - /** - * Checks whether the elements in a {@link String} array are null. - * - * @param strings The strings. - */ - private static void checkNotNull(@NotNull String[] strings, @NotNull String message) { - for (String string : strings) { - Preconditions.checkNotNull(string, message); - } + } + + static class VoidBuilderImpl implements DefaultFunctionImpl.VoidBuilder { + + private final SkriptAddon source; + private final String name; + private final SequencedMap> parameters = new LinkedHashMap<>(); + private final Set modifiers = new HashSet<>(); + + private ch.njol.skript.util.Contract contract = null; + + private String[] description; + private String[] since; + private String[] examples; + private String[] keywords; + private String[] requires; + + VoidBuilderImpl(@NotNull SkriptAddon source, @NotNull String name) { + Preconditions.checkNotNull(source, "source cannot be null"); + Preconditions.checkNotNull(name, "name cannot be null"); + + this.source = source; + this.name = name; + } + + @Override + public VoidBuilder modifiers(Signature.Modifier @NotNull ... modifiers) { + Preconditions.checkNotNull(modifiers, "modifiers cannot be null"); + checkNotNull(modifiers, "modifiers contents cannot be null"); + + this.modifiers.addAll(Set.of(modifiers)); + return this; + } + + @Override + public VoidBuilder contract(@NotNull ch.njol.skript.util.Contract contract) { + Preconditions.checkNotNull(contract, "contract cannot be null"); + + this.contract = contract; + return this; + } + + @Override + public VoidBuilder description(@NotNull String @NotNull ... description) { + Preconditions.checkNotNull(description, "description cannot be null"); + checkNotNull(description, "description contents cannot be null"); + + this.description = description; + return this; + } + + @Override + public VoidBuilder since(@NotNull String @NotNull ... since) { + Preconditions.checkNotNull(since, "since cannot be null"); + checkNotNull(since, "since contents cannot be null"); + + this.since = since; + return this; + } + + @Override + public VoidBuilder examples(@NotNull String @NotNull ... examples) { + Preconditions.checkNotNull(examples, "examples cannot be null"); + checkNotNull(examples, "examples contents cannot be null"); + + this.examples = examples; + return this; + } + + @Override + public VoidBuilder keywords(@NotNull String @NotNull ... keywords) { + Preconditions.checkNotNull(keywords, "keywords cannot be null"); + checkNotNull(keywords, "keywords contents cannot be null"); + + this.keywords = keywords; + return this; + } + + @Override + public VoidBuilder requires(@NotNull String @NotNull ... requires) { + Preconditions.checkNotNull(keywords, "requires cannot be null"); + checkNotNull(keywords, "requires contents cannot be null"); + + this.requires = requires; + return this; + } + + @Override + public VoidBuilder parameter(@NotNull String name, @NotNull Class type, Parameter.Modifier @NotNull ... modifiers) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + + parameters.put(name, new DefaultParameter<>(name, type, modifiers)); + return this; + } + + @Override + public DefaultFunction build(@NotNull Consumer execute) { + Preconditions.checkNotNull(execute, "execute cannot be null"); + + return new DefaultFunctionImpl<>(source, name, modifiers, parameters, contract, + (args) -> { + execute.accept(args); + return null; + }, + description, since, examples, keywords, requires); } } @@ -293,22 +397,47 @@ private static void checkNotNull(@NotNull String[] strings, @NotNull String mess /** * A parameter for a {@link DefaultFunction}. * - * @param name The name. - * @param type The type's class. + * @param name The name. + * @param type The type's class. * @param modifiers The modifiers. - * @param The type. + * @param The type. */ record DefaultParameter(String name, Class type, Set modifiers) implements Parameter { DefaultParameter(String name, Class type, Modifier... modifiers) { - this(name, type, Set.of(modifiers)); + this(name, type, modernizeModifiers(modifiers)); } @Override public @NotNull String toString() { return toFormattedString(); } + + @SuppressWarnings("removal") + private static Set modernizeModifiers(Modifier[] modifiers) { + Set result = new LinkedHashSet<>(); + for (Modifier modifier : modifiers) { + if (modifier instanceof RangedModifier rangedModifier) { + //noinspection rawtypes,unchecked + result.add(new Modifier.Ranged(rangedModifier.getMin(), rangedModifier.getMax())); + } else { + result.add(modifier); + } + } + return result; + } + } + + /** + * Checks whether the elements in an array are null. + * + * @param objects The objects. + */ + private static void checkNotNull(@NotNull Object[] objects, @NotNull String message) { + for (Object object : objects) { + Preconditions.checkNotNull(object, message); + } } } diff --git a/src/main/java/org/skriptlang/skript/common/function/Function.java b/src/main/java/org/skriptlang/skript/common/function/Function.java index 5266f57d2ad..edd4a8bcbe6 100644 --- a/src/main/java/org/skriptlang/skript/common/function/Function.java +++ b/src/main/java/org/skriptlang/skript/common/function/Function.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.ApiStatus.NonExtendable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.util.event.EventRegistry; /** * Represents a function implementation. @@ -21,7 +22,7 @@ public interface Function { /** * Executes this function with the given parameters. * - * @param event The event that is associated with this function execution. + * @param event The event that is associated with this function execution. * @param arguments The arguments to execute the function with. * @return The return value. */ @@ -44,4 +45,39 @@ public interface Function { @Experimental @NotNull String @Nullable [] returnedKeys(); + // + + /** + * Used for listening to function events. + * + * @see #eventRegistry() + */ + interface Event extends org.skriptlang.skript.util.event.Event { + } + + /** + * Called when a function is called. + */ + @FunctionalInterface + interface CallEvent extends Event { + + /** + * The method that is called when this event triggers. + */ + void onCall(Function function); + + } + + /** + * @return An event registry for function events. + */ + static EventRegistry eventRegistry() { + return Data.EVENT_REGISTRY; + } + + class Data { + private static final EventRegistry EVENT_REGISTRY = new EventRegistry<>(); + } + // + } diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionParser.java b/src/main/java/org/skriptlang/skript/common/function/FunctionParser.java index bba55aa7a26..6bdda06428b 100644 --- a/src/main/java/org/skriptlang/skript/common/function/FunctionParser.java +++ b/src/main/java/org/skriptlang/skript/common/function/FunctionParser.java @@ -5,7 +5,6 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.lang.function.Functions; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.skript.util.Utils.PluralResult; @@ -32,9 +31,8 @@ public class FunctionParser { * @param returns The return type of the function, or null if the function should not return anything. * @param local If the signature of function is local. * @return Parsed signature or null if something went wrong. - * @see Functions#registerSignature(ch.njol.skript.lang.function.Signature) */ - public static @Nullable ch.njol.skript.lang.function.Signature parse(String script, String name, String args, @Nullable String returns, boolean local) { + public static @Nullable Signature parse(String script, String name, String args, @Nullable String returns, boolean local) { Parameters parameters = parseParameters(args); if (parameters == null) return null; diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionReference.java b/src/main/java/org/skriptlang/skript/common/function/FunctionReference.java index 9bf0e0a0b61..a429acfc1b1 100644 --- a/src/main/java/org/skriptlang/skript/common/function/FunctionReference.java +++ b/src/main/java/org/skriptlang/skript/common/function/FunctionReference.java @@ -3,9 +3,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.lang.*; import ch.njol.skript.lang.function.FunctionEvent; -import ch.njol.skript.lang.function.FunctionRegistry; -import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; -import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; import ch.njol.skript.lang.function.Functions; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.LiteralUtils; @@ -17,6 +14,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.common.function.FunctionReferenceParser.EmptyExpression; +import org.skriptlang.skript.common.function.FunctionRegistry.Retrieval; +import org.skriptlang.skript.common.function.FunctionRegistry.RetrievalResult; import org.skriptlang.skript.common.function.Parameter.Modifier; import java.util.*; @@ -40,6 +39,29 @@ public final class FunctionReference implements Debuggable { private record ArgInfo(Expression expression, Class type, Set modifiers) { + /** + * Returns whether this parameter has the specified modifier. + * + * @param modifier The modifier. + * @return True when {@link #modifiers()} contains the specified modifier, false if not. + */ + boolean hasModifier(Class modifier) { + return modifiers().stream().anyMatch(modifier::isInstance); + } + + /** + * Gets a modifier of the specified type if present. + * + * @param modifierClass The class of the modifier to retrieve + * @return The modifier instance, or null if not present + */ + M getModifier(Class modifierClass) { + return modifiers().stream() + .filter(modifierClass::isInstance) + .map(modifierClass::cast) + .findFirst() + .orElse(null); + } } public FunctionReference(@Nullable String namespace, @@ -64,9 +86,16 @@ public FunctionReference(@Nullable String namespace, public boolean validate() { if (!validSignature) { Class[] parameters = Arrays.stream(cachedSignature.parameters().all()) - .map(Parameter::type) - .toArray(Class[]::new); - var result = FunctionRegistry.getRegistry().getSignature(namespace, name, parameters); + .map(Parameter::type) + .toArray(Class[]::new); + + Retrieval> result; + if (namespace == null) { + result = Skript.instance().registry(FunctionRegistry.class).getSignature(name, parameters); + } else { + result = Skript.instance().registry(FunctionRegistry.class).getSignature(namespace, name, parameters); + } + if (result.result() == RetrievalResult.EXACT) { //noinspection unchecked cachedSignature = (Signature) result.retrieved(); @@ -77,9 +106,9 @@ public boolean validate() { if (!printedInvalidSignatureWarning) { printedInvalidSignatureWarning = true; Skript.warning(String.format("The function '%s' from the script '%s' no longer exists." - + " Skript will continue to use the old function until this function is registered again." - + " Function call: %s", - name, namespace, toString(null, false))); + + " Skript will continue to use the old function until this function is registered again." + + " Function call: %s", + name, namespace, toString(null, false))); } } } @@ -168,7 +197,7 @@ public T execute(Event event) { SequencedMap args = new LinkedHashMap<>(); cachedArguments.forEach((k, v) -> { - if (v.modifiers().contains(Modifier.KEYED)) { + if (v.hasModifier(Modifier.Keyed.class)) { args.put(k, Classes.clone(evaluateKeyed(v.expression(), event))); return; } @@ -189,6 +218,8 @@ public T execute(Event event) { if (Functions.callFunctionEvents) Bukkit.getPluginManager().callEvent(fnEvent); + org.skriptlang.skript.common.function.Function.eventRegistry().events(Function.CallEvent.class) + .forEach(e -> e.onCall(function)); return function.execute(fnEvent, new FunctionArgumentsImpl(args)); } @@ -253,7 +284,12 @@ public Function function() { .map(Parameter::type) .toArray(Class[]::new); - Retrieval> retrieval = FunctionRegistry.getRegistry().getFunction(namespace, name, parameters); + Retrieval> retrieval; + if (namespace == null) { + retrieval = Skript.instance().registry(FunctionRegistry.class).getFunction(name, parameters); + } else { + retrieval = Skript.instance().registry(FunctionRegistry.class).getFunction(namespace, name, parameters); + } if (retrieval.result() == RetrievalResult.EXACT) { //noinspection unchecked diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java b/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java index f5d93fd7ef1..cda67c3b10f 100644 --- a/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java +++ b/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java @@ -6,8 +6,6 @@ import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.lang.function.FunctionRegistry; -import ch.njol.skript.lang.function.Signature; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.ArgsMessage; @@ -119,16 +117,17 @@ public FunctionReference parseFunctionReference(String name, FunctionRefe } ParserInstance parser = ParserInstance.get(); + Set> options; String namespace; if (parser.isActive()) { namespace = parser.getCurrentScript().getConfig().getFileName(); + options = Skript.instance().registry(FunctionRegistry.class).getSignatures(namespace, name); } else { namespace = null; + options = Skript.instance().registry(FunctionRegistry.class).getSignatures(name); } // try to find a matching signature to get which types to parse args with - Set> options = FunctionRegistry.getRegistry().getSignatures(namespace, name); - if (options.isEmpty()) { doesNotExist(name, arguments, options); log.printError(); @@ -213,7 +212,7 @@ private Set> getExactReferences( Argument[] originalArguments = arguments; for (Signature signature : signatures) { // if arguments arent possible, skip - if (arguments.length > signature.getMaxParameters() || arguments.length < signature.getMinParameters()) { + if (arguments.length > signature.parameters().maxCount() || arguments.length < signature.parameters().minCount()) { continue; } arguments = originalArguments; @@ -258,7 +257,7 @@ private Set> getExactReferences( Expression fallback; if (parameter instanceof ScriptParameter sp) { fallback = sp.defaultValue(); - } else if (parameter.hasModifier(Modifier.OPTIONAL)) { + } else if (parameter.hasModifier(Modifier.Optional.class)) { fallback = new EmptyExpression(); } else { fallback = null; @@ -294,7 +293,8 @@ private Set> getExactReferences( int lastCheck = -1; // the index of the last unnamed argument slot that was filled int lastFilled = -1; - fill: for (int i = 0; i < arguments.length; i++) { + fill: + for (int i = 0; i < arguments.length; i++) { Argument argument = arguments[i]; if (argument.type() == ArgumentType.NAMED) { priorNames.add(argument.name()); @@ -397,7 +397,7 @@ private Set> getListReferences( Expression fallback; if (parameter instanceof ScriptParameter sp) { fallback = sp.defaultValue(); - } else if (parameter.hasModifier(Modifier.OPTIONAL)) { + } else if (parameter.hasModifier(Modifier.Optional.class)) { fallback = new EmptyExpression(); } else { fallback = null; @@ -483,8 +483,8 @@ private void ambiguousError(String name, Set> reference /** * Prints the error for when a function does not exist. * - * @param name The function name. - * @param arguments The passed arguments to the function call. + * @param name The function name. + * @param arguments The passed arguments to the function call. * @param possibleSignatures A set of signatures that may contain what the user intended to match. */ private void doesNotExist(String name, FunctionReference.Argument[] arguments, Set> possibleSignatures) { @@ -520,24 +520,24 @@ private void doesNotExist(String name, FunctionReference.Argument[] argu String possibleMatch = ""; var intended = possibleSignatures.stream() - .filter(signature -> { // filter for signatures that contain all known arguments - List> currentArgumentTypes = new ArrayList<>(argumentTypes); - Arrays.stream(signature.parameters().all()) - .map(parameter -> Utils.getComponentType(parameter.type())) - .forEach(type -> { - var iterator = currentArgumentTypes.iterator(); - while (iterator.hasNext()) { - if (type.isAssignableFrom(iterator.next())) { - iterator.remove(); - break; - } - } - }); - return currentArgumentTypes.isEmpty(); - }) - .min(Comparator.comparingInt(signature -> Math.abs(arguments.length - signature.getMaxParameters()))); + .filter(signature -> { // filter for signatures that contain all known arguments + List> currentArgumentTypes = new ArrayList<>(argumentTypes); + Arrays.stream(signature.parameters().all()) + .map(parameter -> Utils.getComponentType(parameter.type())) + .forEach(type -> { + var iterator = currentArgumentTypes.iterator(); + while (iterator.hasNext()) { + if (type.isAssignableFrom(iterator.next())) { + iterator.remove(); + break; + } + } + }); + return currentArgumentTypes.isEmpty(); + }) + .min(Comparator.comparingInt(signature -> Math.abs(arguments.length - signature.parameters().maxCount()))); if (intended.isPresent()) { - possibleMatch = " " + POTENTIAL_SIGNATURE.toString(intended.get().toString(false, false)); + possibleMatch = " " + POTENTIAL_SIGNATURE.toString(intended.get().toString()); } Skript.error(UNKNOWN_FUNCTION.toString(name, joiner) + possibleMatch); } @@ -625,10 +625,10 @@ private ArgumentParseResult parseFunctionArguments(FunctionReference.Argument parseExpression(Argument argument, ArgumentParseTarget targetData) { if (argument.value() == null) { @@ -641,7 +641,7 @@ private Expression parseExpression(Argument argument, ArgumentParseTa Expression expression = parser.parseExpression(targetData.type()); if (expression == null) { logHandler.printError(INVALID_ARGUMENT.toString( - argument.name(), Classes.getSuperClassInfo(targetData.type()).getName().getSingular(), argument.value() + argument.name(), Classes.getSuperClassInfo(targetData.type()).getName().getSingular(), argument.value() )); } return expression; diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionRegistry.java b/src/main/java/org/skriptlang/skript/common/function/FunctionRegistry.java new file mode 100644 index 00000000000..ad3c1a7b3d1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/function/FunctionRegistry.java @@ -0,0 +1,303 @@ +package org.skriptlang.skript.common.function; + +import ch.njol.skript.SkriptAPIException; +import org.jetbrains.annotations.ApiStatus.Experimental; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.ApiStatus.NonExtendable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; +import org.skriptlang.skript.Skript; +import org.skriptlang.skript.util.Registry; +import org.skriptlang.skript.util.ViewProvider; + +import java.util.Collection; +import java.util.SequencedCollection; +import java.util.Set; + +/** + * A registry for functions. + *

+ * Obtain an instance using {@code SkriptAddon#registry(FunctionRegistry.class)}. + *
+ * Or an unmodifiable view using {@code Skript.instance().registry(FunctionRegistry.class)}. + */ +@NonExtendable +@Internal +@Experimental +public interface FunctionRegistry extends Registry>, ViewProvider { + + /** + * Creates an empty function registry. + * + * @param skript the Skript instance + * @return a new empty function registry + */ + @Contract("_ -> new") + static FunctionRegistry empty(Skript skript) { + return new FunctionRegistryImpl(skript); + } + + /** + * Registers a global signature. + *

+ * Attempting to register a local signature in the global namespace, or a global signature in + * a local namespace, will throw an {@link IllegalArgumentException}. + *

+ * + * @param signature The signature to register. + * @throws SkriptAPIException if a signature with the same name and parameters is already registered + * in this namespace. + * @throws IllegalArgumentException if the signature is local. + */ + void register(@NotNull Signature signature); + + /** + * Registers a local signature. + *

+ * Attempting to register a local signature in the global namespace, or a global signature in + * a local namespace, will throw an {@link IllegalArgumentException}. + *

+ * + * @param namespace The namespace to register the signature in. + * Usually represents the path of the script this signature is registered in. + * @param signature The signature to register. + * @throws SkriptAPIException if a signature with the same name and parameters is already registered + * in this namespace. + * @throws IllegalArgumentException if the signature is global and namespace is not null. + */ + void register(@NotNull String namespace, @NotNull Signature signature); + + /** + * Registers a global function. + * + * @param function The function to register. + * @throws SkriptAPIException if a signature with the same name and parameters is already registered + * in this namespace. + */ + void register(@NotNull Function function); + + /** + * Registers a local function. + * + * @param function The function to register. + * @param namespace The namespace to register the function in. + * Usually represents the path of the script this signature is registered in. + * @throws SkriptAPIException if a signature with the same name and parameters is already registered + * in this namespace. + */ + void register(@NotNull String namespace, @NotNull Function function); + + /** + * The result of attempting to retrieve a function. + * Depending on the type, a {@link Retrieval} will feature different data. + */ + enum RetrievalResult { + + /** + * The specified function or signature has not been registered. + */ + NOT_REGISTERED, + + /** + * There are multiple functions or signatures that may fit the provided name and argument types. + */ + AMBIGUOUS, + + /** + * A single function or signature has been found which matches the name and argument types. + */ + EXACT + + } + + /** + * The result of trying to retrieve a function or signature. + *

+ * When getting a function or signature, the following situations may occur. + * These are specified by {@code type}. + *

    + *
  • + * {@code NOT_REGISTERED}. The specified function or signature is not registered. + * Both {@code retrieved} and {@code conflictingArgs} will be null. + *
  • + *
  • + * {@code AMBIGUOUS}. There are multiple functions or signatures that + * may fit the provided name and argument types. + * {@code retrieved} will be null, and {@code conflictingArgs} + * will contain the conflicting function or signature parameters. + *
  • + *
  • + * {@code EXACT}. A single function or signature has been found which matches the name and argument types. + * {@code retrieved} will contain the function or signature, and {@code conflictingArgs} will be null. + *
  • + *
+ *

+ * + * @param result The result of the function or signature retrieval. + * @param retrieved The function or signature that was found if {@code result} is {@code EXACT}. + * @param conflictingArgs The conflicting arguments if {@code result} is {@code AMBIGUOUS}. + */ + record Retrieval( + @NotNull RetrievalResult result, + T retrieved, + @NotNull Collection>> conflictingArgs + ) { + + /** + * @return Whether this result errored. + */ + public boolean isError() { + return result != RetrievalResult.EXACT; + } + + /** + * @param The type to return. + * @return A retrieval without a registration. + */ + public static Retrieval notRegistered() { + return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, Set.of()); + } + + /** + * @param retrieved The retrieved object. + * @param The type to return. + * @return An exact retrieval. + */ + public static Retrieval exact(T retrieved) { + return new Retrieval<>(RetrievalResult.EXACT, retrieved, Set.of()); + } + + /** + * @param conflictingArgs The function arguments of all conflicting functions. + * @return An ambiguous retrieval. + */ + public static Retrieval ambiguous(Collection>> conflictingArgs) { + return new Retrieval<>(RetrievalResult.AMBIGUOUS, null, conflictingArgs); + } + + } + + /** + * Gets a function. + * + * @param name The name of the function. + * @param args The types of the arguments of the function. + * @return Information related to the attempt to get the specified function, stored in a {@link Retrieval} object. + */ + @NotNull Retrieval> getFunction( + @NotNull String name, + @NotNull Class... args + ); + + /** + * Gets a function. If no local function is found, checks for global functions. + * + * @param namespace The namespace to get the function from. + * Usually represents the path of the script this function is registered in. + * @param name The name of the function. + * @param args The types of the arguments of the function. + * @return Information related to the attempt to get the specified function, stored in a {@link Retrieval} object. + */ + @NotNull Retrieval> getFunction( + @NotNull String namespace, + @NotNull String name, + @NotNull Class... args + ); + + /** + * Gets the signature for a global function with the given name and arguments. + * + * @param name The name of the function. + * @param args The types of the arguments of the function. + * @return Information related to the attempt to get the specified signature, stored in a {@link Retrieval} object. + */ + @NotNull Retrieval> getSignature( + @NotNull String name, + @NotNull Class... args + ); + + /** + * Gets the signature for a function with the given name and arguments. + * If no local function is found, checks for global functions. + * + * @param namespace The namespace to get the function from. + * Usually represents the path of the script this function is registered in. + * @param name The name of the function. + * @param args The types of the arguments of the function. + * @return Information related to the attempt to get the specified signature, stored in a {@link Retrieval} object. + */ + @NotNull Retrieval> getSignature( + @NotNull String namespace, + @NotNull String name, + @NotNull Class... args + ); + + /** + * Gets the signature for a function with the given name and arguments. + * Only checks for global functions. + *

+ * This function checks performs no argument conversions, and is only used for determining whether a + * signature already exists with the exact specified arguments. In almost all cases, {@link #getSignature(String, Class[])} + * should be used. + *

+ * + * @param name The name of the function. + * @param args The types of the arguments of the function. + * @return The signature for the function with the given name and argument types, or null if no such function exists. + */ + Retrieval> getExactSignature( + @NotNull String name, + @NotNull Class... args + ); + + /** + * Gets the signature for a function with the given name and arguments. If no local function is found, + * checks for global functions. + *

+ * This function checks performs no argument conversions, and is only used for determining whether a + * signature already exists with the exact specified arguments. In almost all cases, {@link #getSignature(String, String, Class[])} + * should be used. + *

+ * + * @param namespace The namespace to get the function from. + * Usually represents the path of the script this function is registered in. + * @param name The name of the function. + * @param args The types of the arguments of the function. + * @return The signature for the function with the given name and argument types, or null if no such function exists. + */ + Retrieval> getExactSignature( + @NotNull String namespace, + @NotNull String name, + @NotNull Class... args + ); + + /** + * Gets every signature with the name {@code name} in the global namespace. + * + * @param name The name of the signature(s) to obtain. + * @return A list of all signatures named {@code name}. + */ + @Unmodifiable + @NotNull Set> getSignatures(@NotNull String name); + + /** + * Gets every signature with the name {@code name}. + * This includes global functions and functions registered in the namespace (if valid). + * + * @param namespace The additional namespace to obtain signatures from. + * Usually represents the path of the script this function is registered in. + * @param name The name of the signature(s) to obtain. + * @return A list of all signatures named {@code name}. + */ + @Unmodifiable + @NotNull Set> getSignatures(@NotNull String namespace, @NotNull String name); + + /** + * Removes a function's signature from the registry. + * + * @param signature The signature to remove. + */ + void remove(@NotNull Signature signature); + +} diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionRegistryImpl.java b/src/main/java/org/skriptlang/skript/common/function/FunctionRegistryImpl.java new file mode 100644 index 00000000000..49cdc511dfd --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/function/FunctionRegistryImpl.java @@ -0,0 +1,746 @@ +package org.skriptlang.skript.common.function; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.util.Utils; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.ApiStatus.Experimental; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NonNull; +import org.skriptlang.skript.common.function.Parameter.Modifier; +import org.skriptlang.skript.lang.converter.Converters; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Experimental +@ApiStatus.Internal +public final class FunctionRegistryImpl implements org.skriptlang.skript.common.function.FunctionRegistry { + + private final org.skriptlang.skript.Skript skript; + + public FunctionRegistryImpl(org.skriptlang.skript.Skript skript) { + this.skript = skript; + } + + /** + * The pattern for a valid function name. + * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. + */ + final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile("[\\p{IsAlphabetic}_][\\p{IsAlphabetic}\\d_]*"); + + /** + * The namespace for registered global functions. + */ + private final NamespaceIdentifier GLOBAL_NAMESPACE = new NamespaceIdentifier(null); + + /** + * All registered namespaces. + */ + private final Map namespaces = new ConcurrentHashMap<>(); + + @Override + public Collection> elements() { + Set> functions = new HashSet<>(); + + for (Namespace namespace : namespaces.values()) { + functions.addAll(namespace.functions.values()); + } + + return Collections.unmodifiableSet(functions); + } + + private record UnmodifiableFunctionRegistry( + org.skriptlang.skript.common.function.FunctionRegistry registry) implements org.skriptlang.skript.common.function.FunctionRegistry { + + @Override + public org.skriptlang.skript.common.function.FunctionRegistry unmodifiableView() { + return this; + } + + @Override + public Collection> elements() { + return registry.elements(); + } + + @Override + public void register(@NotNull Signature signature) { + throw new UnsupportedOperationException("Cannot register signatures on an unmodifiable function registry"); + } + + @Override + public void register(@NotNull String namespace, @NotNull Signature signature) { + throw new UnsupportedOperationException("Cannot register signatures on an unmodifiable function registry"); + } + + @Override + public void register(@NotNull Function function) { + throw new UnsupportedOperationException("Cannot register functions on an unmodifiable function registry"); + } + + @Override + public void register(@NotNull String namespace, @NotNull Function function) { + throw new UnsupportedOperationException("Cannot register functions on an unmodifiable function registry"); + } + + @Override + public @NotNull Retrieval> getFunction(@NotNull String name, @NotNull Class... args) { + return registry.getFunction(name, args); + } + + @Override + public @NotNull Retrieval> getFunction(@NotNull String namespace, @NotNull String name, @NotNull Class... args) { + return registry.getFunction(namespace, name, args); + } + + @Override + public @NotNull Retrieval> getSignature(@NotNull String name, @NotNull Class... args) { + return registry.getSignature(name, args); + } + + @Override + public @NotNull Retrieval> getSignature(@NotNull String namespace, @NotNull String name, @NotNull Class... args) { + return registry.getSignature(namespace, name, args); + } + + @Override + public Retrieval> getExactSignature(@NotNull String name, @NonNull @NotNull Class... args) { + return registry.getExactSignature(name, args); + } + + @Override + public Retrieval> getExactSignature(@NotNull String namespace, @NotNull String name, @NonNull @NotNull Class... args) { + return registry.getExactSignature(namespace, name, args); + } + + @Override + public @Unmodifiable @NotNull Set> getSignatures(@NotNull String name) { + return registry.getSignatures(name); + } + + @Override + public @Unmodifiable @NotNull Set> getSignatures(@NotNull String namespace, @NotNull String name) { + return registry.getSignatures(namespace, name); + } + + @Override + public void remove(@NotNull Signature signature) { + throw new UnsupportedOperationException("Cannot remove functions on an unmodifiable function registry"); + } + } + + @Override + public org.skriptlang.skript.common.function.FunctionRegistry unmodifiableView() { + return new UnmodifiableFunctionRegistry(this); + } + + @Override + public void register(@NotNull Signature signature) { + Preconditions.checkNotNull(signature, "signature cannot be null"); + if (signature.hasModifier(Signature.Modifier.Local.class)) { + throw new IllegalArgumentException("Cannot register a local signature in the global namespace"); + } + register(GLOBAL_NAMESPACE, signature); + } + + @Override + public void register(@NotNull String namespace, @NotNull Signature signature) { + Preconditions.checkNotNull(signature, "signature cannot be null"); + if (!signature.hasModifier(Signature.Modifier.Local.class)) { + throw new IllegalArgumentException("Cannot register a global signature in a local namespace"); + } + + register(new NamespaceIdentifier(namespace), signature); + } + + private void register(@NotNull NamespaceIdentifier namespace, @NotNull Signature signature) { + Preconditions.checkArgument(FUNCTION_NAME_PATTERN.matcher(signature.name()).matches(), + "Invalid signature name '%s'".formatted(signature.name())); + Skript.checkAcceptRegistrations(); + + Retrieval> existing; + Parameter[] parameters = signature.parameters().all(); + + if (parameters.length == 1 && !parameters[0].isSingle()) { + existing = getExactSignature(signature.namespace(), signature.name(), parameters[0].type().arrayType()); + } else { + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + types[i] = parameters[i].type(); + } + + existing = getExactSignature(signature.namespace(), signature.name(), types); + } + + // if this function has already been registered, only allow it if one function is local and one is global. + // if both are global or both are local, disallow. + if (existing.result() == RetrievalResult.EXACT && existing.retrieved().hasModifier(Signature.Modifier.Local.class) == signature.hasModifier(Signature.Modifier.Local.class)) { + StringBuilder error = new StringBuilder(); + + if (existing.retrieved().hasModifier(Signature.Modifier.Local.class)) { + error.append("Local function "); + } else { + error.append("Function "); + } + error.append("'%s' with the same argument types already exists".formatted(signature.name())); + if (existing.retrieved().namespace() != null) { + error.append(" in script '%s'.".formatted(existing.retrieved().namespace())); + } else { + error.append("."); + } + + throw new SkriptAPIException(error.toString()); + } + + Skript.debug("Registering signature '%s'", signature.name()); + + if (signature instanceof ch.njol.skript.lang.function.Signature oldSignature) + Functions.registerCompatibilitySignature(oldSignature); + + Namespace ns = namespaces.computeIfAbsent(namespace, n -> new Namespace()); + FunctionIdentifier identifier = FunctionIdentifier.of(signature); + + // register + // since we are getting a set and then updating it, + // avoid race conditions by ensuring only one thread can access this namespace for this operation + synchronized (ns) { + Set identifiersWithName = ns.identifiers.computeIfAbsent(identifier.name, s -> new HashSet<>()); + boolean exists = identifiersWithName.add(identifier); + if (!exists) { + alreadyRegisteredError(signature.name(), identifier, namespace); + } + } + + if (ns.signatures.putIfAbsent(identifier, signature) != null) { + alreadyRegisteredError(signature.name(), identifier, namespace); + } + } + + @Override + public void register(@NotNull Function function) { + Preconditions.checkNotNull(function, "function cannot be null"); + if (function.signature().hasModifier(Signature.Modifier.Local.class)) { + throw new IllegalArgumentException("Cannot register a local function in the global namespace"); + } + register(GLOBAL_NAMESPACE, function); + } + + @Override + public void register(@NotNull String namespace, @NotNull Function function) { + Preconditions.checkNotNull(function, "function cannot be null"); + if (!function.signature().hasModifier(Signature.Modifier.Local.class)) { + throw new IllegalArgumentException("Cannot register a global function in a local namespace"); + } + register(new NamespaceIdentifier(namespace), function); + } + + private void register(@NotNull NamespaceIdentifier namespace, @NotNull Function function) { + Signature signature = function.signature(); + Preconditions.checkArgument(FUNCTION_NAME_PATTERN.matcher(signature.name()).matches(), + "Invalid signature name '%s'".formatted(signature.name())); + Skript.checkAcceptRegistrations(); + + Skript.debug("Registering function '%s'", signature.name()); + + FunctionIdentifier identifier = FunctionIdentifier.of(signature); + if (!signatureExists(namespace, identifier)) { + register(namespace, signature); + } + + Namespace ns = namespaces.computeIfAbsent(namespace, n -> new Namespace()); + + Function existing = ns.functions.putIfAbsent(identifier, function); + if (existing != null) { + alreadyRegisteredError(signature.name(), identifier, namespace); + } + } + + private static void alreadyRegisteredError(String name, FunctionIdentifier identifier, NamespaceIdentifier namespace) { + throw new SkriptAPIException("Function '%s' with parameters %s is already registered in %s" + .formatted(name, Arrays.toString(Arrays.stream(identifier.args).map(Class::getSimpleName).toArray()), + namespace)); + } + + /** + * Checks if a function with the given name and arguments exists in the namespace. + * + * @param namespace The namespace to check in. + * @param identifier The identifier of the function. + * @return True if a function with the given name and arguments exists in the namespace, false otherwise. + */ + private boolean signatureExists(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier identifier) { + Preconditions.checkNotNull(namespace, "namespace cannot be null"); + Preconditions.checkNotNull(identifier, "identifier cannot be null"); + + Namespace ns = namespaces.get(namespace); + if (ns == null) { + return false; + } + + if (!ns.identifiers.containsKey(identifier.name)) { + return false; + } + + for (FunctionIdentifier other : ns.identifiers.get(identifier.name)) { + if (identifier.equals(other)) { + return true; + } + } + + return false; + } + + @Override + public @NotNull Retrieval> getFunction( + @NotNull String namespace, + @NotNull String name, + @NotNull Class... args + ) { + Retrieval> attempt = getFunction(new NamespaceIdentifier(namespace), + FunctionIdentifier.of(name, true, args)); + if (attempt.result() == RetrievalResult.NOT_REGISTERED) { + attempt = getFunction(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); + } + return attempt; + } + + @Override + public @NotNull Retrieval> getFunction(@NotNull String name, @NotNull Class... args) { + return getFunction(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); + } + + /** + * Gets a function from a namespace. + * + * @param namespace The namespace to get the function from. + * Usually represents the path of the script this function is registered in. + * @param provided The provided identifier of the function. + * @return Information related to the attempt to get the specified function, + * stored in a {@link Retrieval} object. + */ + private @NotNull Retrieval> getFunction(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier provided) { + Preconditions.checkNotNull(namespace, "namespace cannot be null"); + Preconditions.checkNotNull(provided, "provided cannot be null"); + + Namespace ns = namespaces.getOrDefault(namespace, new Namespace()); + Set existing = ns.identifiers.get(provided.name); + if (existing == null) { + Skript.debug("No functions named '%s' exist in the '%s' namespace", provided.name, namespace.name); + return Retrieval.notRegistered(); + } + + Set candidates = candidates(provided, existing, false); + if (candidates.isEmpty()) { + Skript.debug("Failed to find a function for '%s'", provided.name); + return Retrieval.notRegistered(); + } else if (candidates.size() == 1) { + if (Skript.debug()) { + Skript.debug("Matched function for '%s': %s", provided.name, candidates.stream().findAny().orElse(null)); + } + return Retrieval.exact(ns.functions.get(candidates.stream().findAny().orElseThrow())); + } else { + if (Skript.debug()) { + String options = candidates.stream().map(Record::toString).collect(Collectors.joining(", ")); + Skript.debug("Failed to match an exact function for '%s'", provided.name); + Skript.debug("Identifier: %s", provided); + Skript.debug("Options: %s", options); + } + return Retrieval.ambiguous( + candidates.stream() + .map(it -> List.of(it.args())) + .collect(Collectors.toSet())); + } + } + + @Override + public @NotNull Retrieval> getSignature(@NotNull String name, @NotNull Class... args) { + return getSignature(null, name, args); + } + + @Override + public @NotNull Retrieval> getSignature( + @Nullable String namespace, + @NotNull String name, + @NotNull Class... args + ) { + Retrieval> attempt = null; + if (namespace != null) { + attempt = getSignature(new NamespaceIdentifier(namespace), + FunctionIdentifier.of(name, true, args), false); + } + if (attempt == null || attempt.result() == RetrievalResult.NOT_REGISTERED) { + attempt = getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args), false); + } + return attempt; + } + + @Override + public @Unmodifiable @NotNull Set> getSignatures(@NotNull String name) { + return getSignatures(null, name); + } + + @Override + public Retrieval> getExactSignature( + @NotNull String name, + @NotNull Class... args + ) { + return getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args), true); + } + + @Override + public Retrieval> getExactSignature( + @NotNull String namespace, + @NotNull String name, + @NotNull Class... args + ) { + Retrieval> attempt = getSignature(new NamespaceIdentifier(namespace), + FunctionIdentifier.of(name, true, args), true); + + if (attempt.result() == RetrievalResult.NOT_REGISTERED) { + attempt = getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args), true); + } + return attempt; + } + + @Override + public @Unmodifiable @NotNull Set> getSignatures(@Nullable String namespace, @NotNull String name) { + Preconditions.checkNotNull(name, "name cannot be null"); + + Map> total = new HashMap<>(); + + // obtain all local functions of "name" + if (namespace != null) { + Namespace local = namespaces.getOrDefault(new NamespaceIdentifier(namespace), new Namespace()); + + for (FunctionIdentifier identifier : local.identifiers.getOrDefault(name, Collections.emptySet())) { + total.putIfAbsent(identifier, local.signatures.get(identifier)); + } + } + + // obtain all global functions of "name" + Namespace global = namespaces.getOrDefault(GLOBAL_NAMESPACE, new Namespace()); + for (FunctionIdentifier identifier : global.identifiers.getOrDefault(name, Collections.emptySet())) { + total.putIfAbsent(identifier, global.signatures.get(identifier)); + } + + return Set.copyOf(total.values()); + } + + /** + * Gets the signature for a function with the given name and arguments. + * + * @param namespace The namespace to get the function from. + * @param provided The provided identifier of the function. + * @param exact When false, will convert arguments to different types to attempt to find a match. + * When true, will not convert arguments. + * @return The signature for the function with the given name and argument types, or null if no such signature exists + * in the specified namespace. + */ + private Retrieval> getSignature(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier provided, boolean exact) { + Preconditions.checkNotNull(namespace, "namespace cannot be null"); + Preconditions.checkNotNull(provided, "provided cannot be null"); + + Namespace ns = namespaces.getOrDefault(namespace, new Namespace()); + if (!ns.identifiers.containsKey(provided.name)) { + Skript.debug("No signatures named '%s' exist in the '%s' namespace", provided.name, namespace.name); + return Retrieval.notRegistered(); + } + + Set candidates = candidates(provided, ns.identifiers.get(provided.name), exact); + if (candidates.isEmpty()) { + Skript.debug("Failed to find a signature for '%s'", provided.name); + return Retrieval.notRegistered(); + } else if (candidates.size() == 1) { + if (Skript.debug()) { + Skript.debug("Matched signature for '%s': %s", + provided.name, ns.signatures.get(candidates.stream().findAny().orElse(null))); + } + return Retrieval.exact(ns.signatures.get(candidates.stream().findAny().orElseThrow())); + } else { + if (Skript.debug()) { + String options = candidates.stream().map(Record::toString).collect(Collectors.joining(", ")); + Skript.debug("Failed to match an exact signature for '%s'", provided.name); + Skript.debug("Identifier: %s", provided); + Skript.debug("Options: %s", options); + } + return Retrieval.ambiguous(candidates.stream() + .map(it -> List.of(it.args())) + .collect(Collectors.toSet())); + } + } + + /** + * Returns an unmodifiable list of candidates for the provided function identifier. + * + * @param provided The provided function. + * @param existing The existing functions with the same name. + * @param exact When false, will convert arguments to different types to attempt to find a match. + * When true, will not convert arguments. + * @return An unmodifiable list of candidates for the provided function. + */ + private static @Unmodifiable @NotNull Set candidates( + @NotNull FunctionIdentifier provided, + Set existing, + boolean exact + ) { + Set candidates = new HashSet<>(); + + candidates: + for (FunctionIdentifier candidate : existing) { + // by this point, all candidates have matching names + + if (Arrays.stream(candidate.args).filter(Class::isArray).count() == 1 + && candidate.args.length == 1 + && candidate.args[0].isArray()) { + // if a function has single list value param, check all types + // make sure all types in the passed array are valid for the array parameter + Class arrayType = candidate.args[0].componentType(); + for (Class arrayArg : provided.args) { + arrayArg = Utils.getComponentType(arrayArg); + + if (!Converters.converterExists(arrayArg, arrayType)) { + continue candidates; + } + } + + candidates.add(candidate); + continue; + } + + // if argument counts are not possible, skip + if (provided.args.length > candidate.args.length + || provided.args.length < candidate.minArgCount) { + continue; + } + + // if the types of the provided arguments do not match the candidate arguments, skip + for (int i = 0; i < provided.args.length; i++) { + // allows single passed values to still match array type in candidate (e.g. clamp) + Class providedType = Utils.getComponentType(provided.args[i]); + + Class candidateType = Utils.getComponentType(candidate.args[i]); + Class providedArg = provided.args[i]; + if (exact) { + if (providedArg != candidateType) { + continue candidates; + } + } else { + if (!Converters.converterExists(providedType, candidateType)) { + continue candidates; + } + } + } + + candidates.add(candidate); + } + + if (candidates.size() <= 1) { + // if there is only one candidate, then return without trying to convert + return Collections.unmodifiableSet(candidates); + } + + // let overloaded(Long, Long) and overloaded(String, String) be two functions. + // the code below allows overloaded(1, {_x}) to match Long, Long and avoid String, String, + // and allow overloaded({_x}, 1) to match Long, Long and avoid String, String + // despite not being an exact match in all arguments, + // since variables have an unknown type at runtime. + Iterator iterator = candidates.iterator(); + while (iterator.hasNext()) { + FunctionIdentifier candidate = iterator.next(); + int argIndex = 0; + + while (argIndex < provided.args.length) { + if (provided.args[argIndex] == Object.class) { + argIndex++; + continue; + } + + if (provided.args[argIndex] != candidate.args[argIndex]) { + iterator.remove(); + break; + } + + argIndex++; + } + } + + return Collections.unmodifiableSet(candidates); + } + + @Override + public void remove(@NotNull Signature signature) { + Preconditions.checkNotNull(signature, "signature cannot be null"); + + String name = signature.name(); + FunctionIdentifier identifier = FunctionIdentifier.of(signature); + + Namespace namespace; + if (signature.hasModifier(Signature.Modifier.Local.class)) { + namespace = namespaces.get(new NamespaceIdentifier(signature.namespace())); + } else { + namespace = namespaces.get(GLOBAL_NAMESPACE); + } + + if (namespace == null) { + return; + } + + for (FunctionIdentifier other : namespace.identifiers.getOrDefault(name, Set.of())) { + if (!identifier.equals(other)) { + continue; + } + + removeUpdateMaps(namespace, other, name); + return; + } + } + + /** + * Updates the maps by removing the provided function identifier from the maps. + * + * @param namespace The namespace + * @param toRemove The identifier to remove + * @param name The name of the function + */ + private void removeUpdateMaps(Namespace namespace, FunctionIdentifier toRemove, String name) { + namespace.identifiers.computeIfPresent(name, (k, set) -> { + if (set.remove(toRemove)) { + Skript.debug("Removed identifier '%s' from %s", toRemove, namespace); + } + return set.isEmpty() ? null : set; + }); + if (namespace.functions.remove(toRemove) != null) { + Skript.debug("Removed function '%s' from %s", toRemove, namespace); + } + if (namespace.signatures.remove(toRemove) != null) { + Skript.debug("Removed signature '%s' from %s", toRemove, namespace); + } + } + + /** + * An identifier for a function namespace. + */ + private record NamespaceIdentifier(@Nullable String name) { + + } + + /** + * The data a namespace contains. + */ + private static final class Namespace { + + /** + * Map for all function names to their identifiers, allowing for quicker lookup. + */ + private final Map> identifiers = new HashMap<>(); + + /** + * Map for all identifier to function combinations. + */ + private final Map> functions = new HashMap<>(); + + /** + * Map for all identifier to signature combinations. + */ + private final Map> signatures = new HashMap<>(); + + } + + /** + * An identifier for a function. + *

Used to differentiate between functions with the same name but different parameters.

+ * + * @param name The name of the function. + * @param args The arguments of the function. + */ + record FunctionIdentifier(@NotNull String name, boolean local, int minArgCount, + @NotNull Class... args) { + + /** + * Returns the identifier for the given arguments. + * + * @param name The name of the function. + * @param args The types of the arguments. + * @return The identifier for the signature. + */ + static FunctionIdentifier of(@NotNull String name, boolean local, @NotNull Class... args) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(args, "args cannot be null"); + + return new FunctionIdentifier(name, local, args.length, args); + } + + /** + * Returns the identifier for the given signature. + * + * @param signature The signature to get the identifier for. + * @return The identifier for the signature. + */ + static FunctionIdentifier of(@NotNull Signature signature) { + Preconditions.checkNotNull(signature, "signature cannot be null"); + + Parameter[] signatureParams = signature.parameters().all(); + Class[] parameters = new Class[signatureParams.length]; + + int optionalArgs = 0; + for (int i = 0; i < signatureParams.length; i++) { + Parameter param = signatureParams[i]; + if (param.hasModifier(Modifier.Optional.class)) { + optionalArgs++; + } + + parameters[i] = param.type(); + } + + return new FunctionIdentifier(signature.name(), signature.hasModifier(Signature.Modifier.Local.class), + parameters.length - optionalArgs, parameters); + } + + @Override + public int hashCode() { + return Objects.hash(name, Arrays.hashCode(args)); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof FunctionIdentifier other)) { + return false; + } + + if (!name.equals(other.name)) { + return false; + } + + if (args.length != other.args.length) { + return false; + } + + for (int i = 0; i < args.length; i++) { + if (args[i] != other.args[i]) { + return false; + } + } + + return true; + } + + @Override + public @NotNull String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .add("local", local) + .add("minArgCount", minArgCount) + .add("args", Arrays.stream(args).map(Class::getSimpleName).collect(Collectors.joining(", "))) + .toString(); + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/function/Parameter.java b/src/main/java/org/skriptlang/skript/common/function/Parameter.java index 8c49ae879a3..70806c252ee 100644 --- a/src/main/java/org/skriptlang/skript/common/function/Parameter.java +++ b/src/main/java/org/skriptlang/skript/common/function/Parameter.java @@ -12,10 +12,11 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; import org.skriptlang.skript.common.function.DefaultFunction.Builder; -import org.skriptlang.skript.common.function.Parameter.Modifier.RangedModifier; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; +import org.skriptlang.skript.util.Priority; +import java.util.NoSuchElementException; import java.util.Set; import java.util.StringJoiner; @@ -40,28 +41,54 @@ public interface Parameter { /** * @return All modifiers belonging to this parameter. */ - @Unmodifiable @NotNull Set modifiers(); + @Unmodifiable + @NotNull Set modifiers(); + + /** + * @deprecated Use {@link #hasModifier(Class)} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") + default boolean hasModifier(Modifier modifier) { + return modifiers().contains(modifier); + } /** * Returns whether this parameter has the specified modifier. + * * @param modifier The modifier. * @return True when {@link #modifiers()} contains the specified modifier, false if not. */ - default boolean hasModifier(Modifier modifier) { - return modifiers().contains(modifier); + default boolean hasModifier(Class modifier) { + return modifiers().stream().anyMatch(modifier::isInstance); } /** * Gets a modifier of the specified type if present. + * * @param modifierClass The class of the modifier to retrieve * @return The modifier instance, or null if not present */ default M getModifier(Class modifierClass) { return modifiers().stream() - .filter(modifierClass::isInstance) - .map(modifierClass::cast) - .findFirst() - .orElse(null); + .filter(modifierClass::isInstance) + .map(modifierClass::cast) + .findFirst() + .orElseThrow(() -> new NoSuchElementException("No value present for modifier " + modifierClass.getSimpleName())); + } + + /** + * @return Whether this parameter is for single values. + */ + default boolean isSingle() { + return !type().isArray(); + } + + /** + * @deprecated Use {@link #isSingle()} instead. + */ + @Deprecated(forRemoval = true, since = "2.14") + default boolean single() { + return isSingle(); } /** @@ -73,7 +100,7 @@ default M getModifier(Class modifierClass) { * otherwise, the returned array will contain only the values.

* * @param argument the argument passed to this parameter; or {@code null} to use the default value if present - * @param event the event in which to evaluate the expression + * @param event the event in which to evaluate the expression * @return an object array containing either value-only elements or {@code KeyedValue[]} when keyed * @throws IllegalStateException if the argument is {@code null} and this parameter does not have a default value */ @@ -88,7 +115,7 @@ default Object[] evaluate(@Nullable Expression argument, Event even for (int i = 0; i < values.length; i++) values[i] = Classes.clone(values[i]); - if (!hasModifier(Modifier.KEYED)) + if (!hasModifier(Modifier.Keyed.class)) return values; String[] keys = KeyProviderExpression.areKeysRecommended(argument) @@ -97,31 +124,23 @@ default Object[] evaluate(@Nullable Expression argument, Event even return KeyedValue.zip(values, keys); } - /** - * @return Whether this parameter is for single values. - */ - default boolean isSingle() { - return !type().isArray(); - } - - /** - * @deprecated Use {@link #isSingle()} instead. - */ - @Deprecated(forRemoval = true, since = "2.14") - default boolean single() { - return isSingle(); - } - /** * @return A human-readable string representing this parameter. */ - default String toFormattedString() { + default @NotNull String toFormattedString() { StringJoiner joiner = new StringJoiner(" "); joiner.add("%s:".formatted(name())); - if (hasModifier(Modifier.OPTIONAL)) { - joiner.add("optional"); + for (Modifier modifier : modifiers()) { + if (!modifier.toStringPriority().isBefore(Modifier.TYPE_PRIORITY)) { + continue; + } + String string = modifier.toFormattedString(); + if (string.isEmpty()) { + continue; + } + joiner.add(string); } Noun exact = Classes.getSuperClassInfo(type()).getName(); @@ -131,12 +150,15 @@ default String toFormattedString() { joiner.add(exact.getSingular()); } - if (hasModifier(Modifier.RANGED)) { - RangedModifier range = getModifier(RangedModifier.class); - joiner.add("between") - .add(range.getMin().toString()) - .add("and") - .add(range.getMax().toString()); + for (Modifier modifier : modifiers()) { + if (!modifier.toStringPriority().isAfter(Modifier.TYPE_PRIORITY)) { + continue; + } + String string = modifier.toFormattedString(); + if (string.isEmpty()) { + continue; + } + joiner.add(string); } return joiner.toString(); @@ -149,16 +171,75 @@ default String toFormattedString() { interface Modifier { /** - * @return A new Modifier instance to be used as a custom flag. + * The priority used for printing the type in a parameter's string representation. + */ + Priority TYPE_PRIORITY = Priority.base(); + + /** + * @return The modifier as a human-readable, formatted string. */ + @NotNull String toFormattedString(); + + /** + * The priority used when converting this modifier to a string representation. + * + *

+ * Registering after {@link #TYPE_PRIORITY} will print after the type, + * e.g. {@code x: number optional}. + * Registering before {@link #TYPE_PRIORITY} will print before the type, + * e.g. {@code x: optional number}. + *

+ * + * @return The priority used. + */ + @NotNull Priority toStringPriority(); + + /** + * @deprecated Create a new class implementing {@link Modifier} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") static Modifier of() { - return new Modifier() { }; + return new Modifier() { + + private static final Priority PRIORITY = Priority.after(TYPE_PRIORITY); + + @Override + public @NotNull String toFormattedString() { + return ""; + } + + @Override + public @NotNull Priority toStringPriority() { + return PRIORITY; + } + + }; } /** * The modifier for parameters that are optional. */ - Modifier OPTIONAL = of(); + class Optional implements Modifier { + + private static final Priority PRIORITY = Priority.before(TYPE_PRIORITY); + + @Override + public @NotNull String toFormattedString() { + return "optional"; + } + + @Override + public @NotNull Priority toStringPriority() { + return PRIORITY; + } + + } + + /** + * @deprecated Use {@link Optional} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") + Modifier OPTIONAL = new Modifier.Optional(); /** * The modifier for parameters that support optional keyed expressions. @@ -166,33 +247,120 @@ static Modifier of() { * @see ch.njol.skript.lang.KeyProviderExpression * @see ch.njol.skript.lang.KeyReceiverExpression */ - Modifier KEYED = of(); + class Keyed implements Modifier { + + private static final Priority PRIORITY = Priority.before(TYPE_PRIORITY); + + @Override + public @NotNull String toFormattedString() { + return "keyed"; + } + + @Override + public @NotNull Priority toStringPriority() { + return PRIORITY; + } + + } + + /** + * @deprecated Use {@link Keyed} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") + Modifier KEYED = new Modifier.Keyed(); /** * A modifier to use for checking if a parameter is ranged. - * Do NOT use for declaring a parameter to be ranged, use {@link Modifier#ranged(Comparable, Comparable)} - * Accessing the min and the max values can be done via {@link Parameter#getModifier(Class)}. */ - Modifier RANGED = new RangedModifier<>(0,0); // 0 and 0 are just dummy values. + record Ranged>(@NotNull T min, @NotNull T max) implements Modifier { + + private static final Priority PRIORITY = Priority.after(TYPE_PRIORITY); + + /** + * Inclusive range between min and max + * + * @param min min value + * @param max max value + */ + public Ranged { + Preconditions.checkNotNull(min, "min cannot be null"); + Preconditions.checkNotNull(max, "max cannot be null"); + Preconditions.checkState(min.compareTo(max) < 1, "Min value cannot be greater than max value!"); + } + + /** + * @param input The value to test. + * @return Whether input is between min and max. + */ + @SuppressWarnings("unchecked") + public boolean inRange(Object input) { + // convert to right type + if (!min.getClass().isInstance(input)) { + Converter converter = (Converter) Converters.getConverter(input.getClass(), min.getClass()); + if (converter == null) + return false; + input = converter.convert(input); + if (input == null) + return false; + } + // compare + return ((T) input).compareTo(min) > -1 && ((T) input).compareTo(max) < 1; + } + + /** + * @param inputs The values to test. + * @return Whether all the inputs are between min and max. + */ + public boolean inRange(Object @NotNull [] inputs) { + if (inputs.length == 0) + return false; + for (Object input : inputs) { + if (!inRange(input)) + return false; + } + return true; + } + + @Override + public @NotNull String toFormattedString() { + return "between %s and %s".formatted(min, max); + } + + @Override + public @NotNull Priority toStringPriority() { + return PRIORITY; + } + + } + + /** + * @deprecated Use {@link Ranged} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") + Modifier RANGED = new RangedModifier<>(0, 0); // 0 and 0 are just dummy values. /** - * Creates a range modifier with inclusive min and max bounds. + * @deprecated Use {@link Ranged} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") static > RangedModifier ranged(T min, T max) { return new RangedModifier<>(min, max); } /** - * Modifier specifying valid range bounds for numeric parameters. - * Note that ALL instances will have the same hashCode and will be equal to {@link Modifier#RANGED}. - * Avoid comparing these objects or putting multiple into a HashSet or HashMap! + * @deprecated Use {@link Ranged} instead. */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") class RangedModifier> implements Modifier { + + private static final Priority PRIORITY = Priority.before(TYPE_PRIORITY); + private final T min; private final T max; /** * Inclusive range between min and max + * * @param min min value * @param max max value */ @@ -252,7 +420,7 @@ public boolean inRange(Object @NotNull [] inputs) { @Override public boolean equals(Object obj) { // equal to the RANGED singleton for hasModifier checks - return obj == Modifier.RANGED || ((obj instanceof RangedModifier range) && (this == Modifier.RANGED || range.max == this.max && range.min == this.min)); + return obj == Modifier.RANGED || ((obj instanceof RangedModifier range) && (this == Modifier.RANGED || range.max == this.max && range.min == this.min)); } @Override @@ -265,7 +433,18 @@ public String toString() { return "RangedModifier(min=" + min + ", max=" + max + ")"; } + @Override + public @NotNull String toFormattedString() { + return "between %s and %s".formatted(min, max); + } + + @Override + public @NotNull Priority toStringPriority() { + return PRIORITY; + } + } + } } diff --git a/src/main/java/org/skriptlang/skript/common/function/Parameters.java b/src/main/java/org/skriptlang/skript/common/function/Parameters.java index ebb65867ba7..b349b64ea5f 100644 --- a/src/main/java/org/skriptlang/skript/common/function/Parameters.java +++ b/src/main/java/org/skriptlang/skript/common/function/Parameters.java @@ -1,10 +1,12 @@ package org.skriptlang.skript.common.function; +import com.google.common.collect.Lists; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnmodifiableView; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedList; import java.util.SequencedMap; /** @@ -12,17 +14,31 @@ */ public final class Parameters { + private final int maxCount; + private int minCount = 0; private final SequencedMap> named; private final Parameter[] indexed; public Parameters(SequencedMap> parameters) { this.named = parameters; + this.maxCount = parameters.size(); - indexed = new Parameter[parameters.size()]; - int i = 0; - for (Parameter parameter : parameters.values()) { - indexed[i] = parameter; - i++; + this.indexed = new Parameter[this.maxCount]; + { + int i = 0; + for (Parameter parameter : parameters.values()) { + this.indexed[i] = parameter; + i++; + } + } + + int j = size() - 1; + for (Parameter parameter : Lists.reverse(new LinkedList<>(parameters.values()))) { + if (!parameter.hasModifier(Parameter.Modifier.Optional.class)) { + this.minCount = j + 1; + break; + } + j--; } } @@ -64,7 +80,21 @@ public Parameter[] all() { * @return The amount of parameters. */ public int size() { - return indexed.length; + return maxCount; + } + + /** + * @return The most amount of parameters this function supports. + */ + public int maxCount() { + return maxCount; + } + + /** + * @return The least amount of parameters this function supports. + */ + public int minCount() { + return minCount; } /** diff --git a/src/main/java/org/skriptlang/skript/common/function/ScriptParameter.java b/src/main/java/org/skriptlang/skript/common/function/ScriptParameter.java index 6f4b586090f..563f259fdda 100644 --- a/src/main/java/org/skriptlang/skript/common/function/ScriptParameter.java +++ b/src/main/java/org/skriptlang/skript/common/function/ScriptParameter.java @@ -67,10 +67,10 @@ public static Parameter parse(@NotNull String name, @NotNull Class type, @ Set modifiers = new HashSet<>(); if (defaultValue != null) { - modifiers.add(Modifier.OPTIONAL); + modifiers.add(new Modifier.Optional()); } if (type.isArray()) { - modifiers.add(Modifier.KEYED); + modifiers.add(new Modifier.Keyed()); } return new ScriptParameter<>(name, type, defaultValue, modifiers.toArray(new Modifier[0])); @@ -94,7 +94,7 @@ public ScriptParameter(String name, Class type, Expression defaultValue, M */ public Object[] evaluate(@Nullable Expression argument, Event event) { if (argument == null) { - if (!hasModifier(Modifier.OPTIONAL)) { + if (!hasModifier(Modifier.Optional.class)) { throw new IllegalStateException("This parameter is required, but no argument was provided"); } else if (defaultValue == null) { throw new IllegalStateException("This parameter does not have a default value"); diff --git a/src/main/java/org/skriptlang/skript/common/function/Signature.java b/src/main/java/org/skriptlang/skript/common/function/Signature.java index 3b64b177b67..ee463130fd6 100644 --- a/src/main/java/org/skriptlang/skript/common/function/Signature.java +++ b/src/main/java/org/skriptlang/skript/common/function/Signature.java @@ -1,12 +1,22 @@ package org.skriptlang.skript.common.function; +import ch.njol.skript.localization.Noun; +import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Contract; +import ch.njol.util.StringUtils; +import com.google.common.base.Preconditions; import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.ApiStatus.NonExtendable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnmodifiableView; +import org.jetbrains.annotations.Unmodifiable; +import org.skriptlang.skript.common.function.Signature.Modifier.Returns; +import org.skriptlang.skript.util.Priority; + +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.StringJoiner; /** * Represents a function signature. @@ -20,14 +30,32 @@ public interface Signature { /** - * @return The type of this parameter. + * @return The name of the function. */ - @Nullable Class returnType(); + @NotNull String name(); + + /** + * @deprecated Use {@link #hasModifier(Class)} and {@link #getModifier(Class)} + * with {@link Returns} instead. + */ + @Deprecated(forRemoval = true, since = "INSERT VERSION") + default @Nullable Class returnType() { + if (!hasModifier(Modifier.Returns.class)) + return null; + + //noinspection unchecked + return (Class) getModifier(Returns.class).type(); + } /** * @return An unmodifiable view of all the parameters that this signature has. */ - @UnmodifiableView @NotNull Parameters parameters(); + @NotNull Parameters parameters(); + + /** + * @return The namespace of this signature. + */ + @Nullable String namespace(); /** * @return The contract of this signature. @@ -37,6 +65,7 @@ public interface Signature { /** * Adds a reference to the clearing list. + * * @param reference The reference. */ @Experimental @@ -46,10 +75,150 @@ public interface Signature { * @return Whether this signature returns single values. */ default boolean isSingle() { - if (returnType() == null) { + if (!hasModifier(Modifier.Returns.class)) return false; + + return !getModifier(Returns.class).type().isArray(); + } + + /** + * @return All modifiers belonging to this signature. + */ + @Unmodifiable + @NotNull Set modifiers(); + + /** + * Returns whether this signature has the specified modifier. + * + * @param modifier The modifier. + * @return True when {@link #modifiers()} contains the specified modifier, false if not. + */ + default boolean hasModifier(Class modifier) { + return modifiers().stream().anyMatch(modifier::isInstance); + } + + /** + * Gets a modifier of the specified type if present. + * + * @param modifierClass The class of the modifier to retrieve + * @return The modifier instance, or null if not present + */ + default M getModifier(Class modifierClass) { + return modifiers().stream() + .filter(modifierClass::isInstance) + .map(modifierClass::cast) + .findAny() + .orElseThrow(() -> new NoSuchElementException("No value present for modifier " + modifierClass.getSimpleName())); + } + + /** + * @return A human-readable string representing this parameter. + */ + default @NotNull String toFormattedString() { + StringJoiner joiner = new StringJoiner(" "); + + for (Modifier modifier : modifiers()) { + if (!modifier.toStringPriority().isBefore(Modifier.FUNCTION_PRIORITY)) { + continue; + } + String string = modifier.toFormattedString(); + if (string.isEmpty()) { + continue; + } + joiner.add(string); } - return !returnType().isArray(); + + joiner.add(name()); + joiner.add("(%s)".formatted(StringUtils.join(parameters().all(), ", "))); + + for (Modifier modifier : modifiers()) { + if (!modifier.toStringPriority().isAfter(Modifier.FUNCTION_PRIORITY)) { + continue; + } + String string = modifier.toFormattedString(); + if (string.isEmpty()) { + continue; + } + joiner.add(string); + } + + return joiner.toString(); + } + + /** + * Represents a modifier that can be applied to a function signature. + */ + interface Modifier { + + /** + * The priority used for printing the type in a signature's string representation. + */ + Priority FUNCTION_PRIORITY = Priority.base(); + + /** + * @return The modifier as a human-readable, formatted string. + */ + @NotNull String toFormattedString(); + + /** + * The priority used when converting this modifier to a string representation. + * + *

+ * Registering after {@link #FUNCTION_PRIORITY} will print after the function, + * e.g. {@code function x() local}. + * Registering before {@link #FUNCTION_PRIORITY} will print before the function, + * e.g. {@code local function x()}. + *

+ * + * @return The priority used. + */ + @NotNull Priority toStringPriority(); + + record Local(@NotNull String namespace) implements Modifier { + + public Local { + Preconditions.checkNotNull(namespace, "namespace cannot be null"); + } + + private static final Priority PRIORITY = Priority.before(FUNCTION_PRIORITY); + + @Override + public @NotNull String toFormattedString() { + return "local"; + } + + @Override + public @NotNull Priority toStringPriority() { + return PRIORITY; + } + + } + + record Returns(@NotNull Class type) implements Modifier { + + private static final Priority PRIORITY = Priority.after(FUNCTION_PRIORITY); + + public Returns { + Preconditions.checkNotNull(type, "type cannot be null"); + } + + @Override + public @NotNull String toFormattedString() { + Noun exact = Classes.getSuperClassInfo(type).getName(); + if (type.isArray()) { + return "returns " + exact.getPlural(); + } else { + return "returns " + exact.getSingular(); + } + } + + @Override + public @NotNull Priority toStringPriority() { + return PRIORITY; + } + + } + } } diff --git a/src/main/java/org/skriptlang/skript/util/Priority.java b/src/main/java/org/skriptlang/skript/util/Priority.java index b0f49546a67..6edcce277ed 100644 --- a/src/main/java/org/skriptlang/skript/util/Priority.java +++ b/src/main/java/org/skriptlang/skript/util/Priority.java @@ -50,4 +50,31 @@ static Priority after(Priority priority) { */ @Unmodifiable Collection before(); + /** + * Checks whether this priority is before another priority. + * @param other A priority. + * @return True if the priority provided is before this priority, false if not. + */ + default boolean isBefore(Priority other) { + return compareTo(other) < 0; + } + + /** + * Checks whether this priority is equal to another priority. + * @param other A priority. + * @return True if the priority provided is equal to this priority, false if not. + */ + default boolean isEqual(Priority other) { + return compareTo(other) == 0; + } + + /** + * Checks whether this priority is after another priority. + * @param other A priority. + * @return True if the priority provided is after this priority, false if not. + */ + default boolean isAfter(Priority other) { + return compareTo(other) > 0; + } + } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index fae9ec3e6fa..89543e79207 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -29,7 +29,7 @@ website: https://skriptlang.org main: ch.njol.skript.Skript version: @version@ -api-version: 1.21 +api-version: 1.21.5 commands: skript: diff --git a/src/test/java/ch/njol/skript/lang/function/FunctionRegistryTest.java b/src/test/java/ch/njol/skript/lang/function/FunctionRegistryTest.java deleted file mode 100644 index d4896dbf59e..00000000000 --- a/src/test/java/ch/njol/skript/lang/function/FunctionRegistryTest.java +++ /dev/null @@ -1,466 +0,0 @@ -package ch.njol.skript.lang.function; - -import ch.njol.skript.SkriptAPIException; -import ch.njol.skript.lang.function.FunctionRegistry.FunctionIdentifier; -import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; -import ch.njol.skript.lang.util.SimpleLiteral; -import ch.njol.skript.registrations.DefaultClasses; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class FunctionRegistryTest { - - private static final FunctionRegistry registry = FunctionRegistry.getRegistry(); - private static final String FUNCTION_NAME = "testFunctionRegistry"; - private static final String TEST_SCRIPT = "test"; - - private static final Function TEST_FUNCTION = new SimpleJavaFunction<>(FUNCTION_NAME, new Parameter[0], - DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - @Test - public void testGetFunctionRetrieval() { - assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - - assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getSignature(null, FUNCTION_NAME).conflictingArgs()); - - assertEquals(RetrievalResult.NOT_REGISTERED, registry.getFunction(null, FUNCTION_NAME).result()); - assertNull(registry.getFunction(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME).conflictingArgs()); - - registry.register(null, TEST_FUNCTION); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - - assertEquals(RetrievalResult.EXACT, registry.getSignature(null, FUNCTION_NAME).result()); - assertEquals(TEST_FUNCTION.getSignature(), registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getSignature(null, FUNCTION_NAME).conflictingArgs()); - - assertEquals(RetrievalResult.EXACT, registry.getFunction(null, FUNCTION_NAME).result()); - assertEquals(TEST_FUNCTION, registry.getFunction(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME).conflictingArgs()); - - registry.remove(TEST_FUNCTION.getSignature()); - } - - @Test - public void testSimpleMultipleRegistrationsFunction() { - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.register(null, TEST_FUNCTION); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertEquals(TEST_FUNCTION.getSignature(), registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertEquals(TEST_FUNCTION, registry.getFunction(null, FUNCTION_NAME).retrieved()); - - assertThrows(SkriptAPIException.class, () -> registry.register(null, TEST_FUNCTION)); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertEquals(TEST_FUNCTION.getSignature(), registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertEquals(TEST_FUNCTION, registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.remove(TEST_FUNCTION.getSignature()); - } - - @Test - public void testSimpleRegisterRemoveRegisterGlobal() { - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.register(null, TEST_FUNCTION); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertEquals(TEST_FUNCTION.getSignature(), registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertEquals(TEST_FUNCTION, registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.remove(TEST_FUNCTION.getSignature()); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.register(null, TEST_FUNCTION); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertEquals(TEST_FUNCTION.getSignature(), registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertEquals(TEST_FUNCTION, registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.remove(TEST_FUNCTION.getSignature()); - } - - private static final Function LOCAL_TEST_FUNCTION = new SimpleJavaFunction<>(TEST_SCRIPT, FUNCTION_NAME, new Parameter[0], - DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - @Test - public void testSimpleRegisterRemoveRegisterLocal() { - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertEquals(LOCAL_TEST_FUNCTION.getSignature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).retrieved()); - assertEquals(LOCAL_TEST_FUNCTION, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME).retrieved()); - - registry.remove(LOCAL_TEST_FUNCTION.getSignature()); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).retrieved()); - assertNull(registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION); - registry.register(null, TEST_FUNCTION); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); - assertEquals(LOCAL_TEST_FUNCTION.getSignature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).retrieved()); - assertEquals(LOCAL_TEST_FUNCTION, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME).retrieved()); - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - assertEquals(TEST_FUNCTION.getSignature(), registry.getSignature(null, FUNCTION_NAME).retrieved()); - assertEquals(TEST_FUNCTION, registry.getFunction(null, FUNCTION_NAME).retrieved()); - - registry.remove(LOCAL_TEST_FUNCTION.getSignature()); - registry.remove(TEST_FUNCTION.getSignature()); - } - - private static final Function TEST_FUNCTION_B = new SimpleJavaFunction<>(FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.BOOLEAN, true, null) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - private static final Function TEST_FUNCTION_N = new SimpleJavaFunction<>(FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - @Test - public void testMultipleRegistrations() { - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.register(null, TEST_FUNCTION_B); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertEquals(TEST_FUNCTION_B.getSignature(), registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertEquals(TEST_FUNCTION_B, registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.register(null, TEST_FUNCTION_N); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertEquals(TEST_FUNCTION_B.getSignature(), registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertEquals(TEST_FUNCTION_B, registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertEquals(TEST_FUNCTION_N.getSignature(), registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertEquals(TEST_FUNCTION_N, registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - assertThrows(SkriptAPIException.class, () -> registry.register(null, TEST_FUNCTION_B)); - assertThrows(SkriptAPIException.class, () -> registry.register(null, TEST_FUNCTION_N)); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertEquals(TEST_FUNCTION_B.getSignature(), registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertEquals(TEST_FUNCTION_B, registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertEquals(TEST_FUNCTION_N.getSignature(), registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertEquals(TEST_FUNCTION_N, registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.remove(TEST_FUNCTION_B.getSignature()); - registry.remove(TEST_FUNCTION_N.getSignature()); - } - - @Test - public void testRegisterRemoveRegisterGlobal() { - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.register(null, TEST_FUNCTION_B); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertEquals(TEST_FUNCTION_B.getSignature(), registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertEquals(TEST_FUNCTION_B, registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.remove(TEST_FUNCTION_B.getSignature()); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.register(null, TEST_FUNCTION_N); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertEquals(TEST_FUNCTION_N.getSignature(), registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertEquals(TEST_FUNCTION_N, registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.remove(TEST_FUNCTION_N.getSignature()); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Number.class).retrieved()); - - registry.remove(TEST_FUNCTION_B.getSignature()); - registry.remove(TEST_FUNCTION_N.getSignature()); - } - - private static final Function LOCAL_TEST_FUNCTION_B = new SimpleJavaFunction<>(TEST_SCRIPT, FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.BOOLEAN, true, null) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - private static final Function LOCAL_TEST_FUNCTION_N = new SimpleJavaFunction<>(TEST_SCRIPT, FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - @Test - public void testRegisterRemoveRegisterLocal() { - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - - registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION_B); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); - assertEquals(LOCAL_TEST_FUNCTION_B.getSignature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertEquals(LOCAL_TEST_FUNCTION_B, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - - registry.remove(LOCAL_TEST_FUNCTION_B.getSignature()); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - - registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION_N); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Number.class).result()); - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); - assertEquals(LOCAL_TEST_FUNCTION_N.getSignature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - assertEquals(LOCAL_TEST_FUNCTION_N, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - - registry.remove(LOCAL_TEST_FUNCTION_N.getSignature()); - - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); - assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); - assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - - registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION_N); - registry.register(null, TEST_FUNCTION_B); - - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); - assertEquals(LOCAL_TEST_FUNCTION_N.getSignature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - assertEquals(LOCAL_TEST_FUNCTION_N, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); - assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Boolean.class).result()); - assertEquals(TEST_FUNCTION_B.getSignature(), registry.getSignature(null, FUNCTION_NAME, Boolean.class).retrieved()); - assertEquals(TEST_FUNCTION_B, registry.getFunction(null, FUNCTION_NAME, Boolean.class).retrieved()); - - registry.remove(LOCAL_TEST_FUNCTION_N.getSignature()); - registry.remove(TEST_FUNCTION_B.getSignature()); - } - - @Test - public void testIdentifierEmptyOf() { - FunctionIdentifier identifier = FunctionIdentifier.of(FUNCTION_NAME, true); - - assertEquals(FUNCTION_NAME, identifier.name()); - assertTrue(identifier.local()); - assertEquals(0, identifier.minArgCount()); - assertArrayEquals(new Class[0], identifier.args()); - - assertEquals(FunctionIdentifier.of(FUNCTION_NAME, true), identifier); - } - - @Test - public void testIdentifierOf() { - FunctionIdentifier identifier = FunctionIdentifier.of(FUNCTION_NAME, true, Boolean.class, Number.class); - - assertEquals(FUNCTION_NAME, identifier.name()); - assertTrue(identifier.local()); - assertEquals(2, identifier.minArgCount()); - assertArrayEquals(new Class[]{Boolean.class, Number.class}, identifier.args()); - - assertEquals(FunctionIdentifier.of(FUNCTION_NAME, true, Boolean.class, Number.class), identifier); - } - - @Test - public void testIdentifierSignatureOf() { - SimpleJavaFunction function = new SimpleJavaFunction<>(FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.BOOLEAN, true, null), - new Parameter<>("b", DefaultClasses.NUMBER, false, new SimpleLiteral(1, true)) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - FunctionIdentifier identifier = FunctionIdentifier.of(function.getSignature()); - - assertEquals(FUNCTION_NAME, identifier.name()); - assertFalse(identifier.local()); - assertEquals(1, identifier.minArgCount()); - assertArrayEquals(new Class[]{Boolean.class, Number[].class}, identifier.args()); - - SimpleJavaFunction function2 = new SimpleJavaFunction<>(FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.BOOLEAN, true, null), - new Parameter<>("b", DefaultClasses.NUMBER, false, null) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - assertEquals(FunctionIdentifier.of(function2.getSignature()), identifier); - } - - // see https://github.com/SkriptLang/Skript/pull/8015 - @Test - public void testRemoveGlobalScriptFunctions8015() { - // create empty TEST_SCRIPT namespace such that it is not null - registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION); - registry.remove(LOCAL_TEST_FUNCTION.getSignature()); - - assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); - - // construct a global function with a non-null script, which happens in script functions - Signature signature = new Signature<>(TEST_SCRIPT, FUNCTION_NAME, new Parameter[0], - false, DefaultClasses.BOOLEAN, true, ""); - SimpleJavaFunction fn = new SimpleJavaFunction<>(signature) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[] { true }; - } - }; - - // ensure new behaviour - assertThrows(IllegalArgumentException.class, () -> registry.register(TEST_SCRIPT, fn)); - - registry.register(null, fn); - - assertEquals(RetrievalResult.EXACT, registry.getSignature(null, FUNCTION_NAME).result()); - - registry.remove(signature); - - assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); - } - - private static final Function TEST_FUNCTION_P = new SimpleJavaFunction<>(FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.PLAYER, true, null) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - private static final Function TEST_FUNCTION_OP = new SimpleJavaFunction<>(FUNCTION_NAME, - new Parameter[]{ - new Parameter<>("a", DefaultClasses.OFFLINE_PLAYER, true, null) - }, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean @Nullable [] executeSimple(Object[][] params) { - return new Boolean[]{true}; - } - }; - - @Test - public void testGetExactSignature() { - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Player.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, Player.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, Player.class).retrieved()); - assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, OfflinePlayer.class).result()); - assertNull(registry.getSignature(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); - assertNull(registry.getFunction(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); - - registry.register(null, TEST_FUNCTION_P); - - assertSame(RetrievalResult.EXACT, registry.getExactSignature(null, FUNCTION_NAME, Player.class).result()); - assertEquals(TEST_FUNCTION_P.getSignature(), registry.getExactSignature(null, FUNCTION_NAME, Player.class).retrieved()); - assertNull(registry.getExactSignature(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); - - assertEquals(TEST_FUNCTION_P.getSignature(), registry.getSignature(null, FUNCTION_NAME, Player.class).retrieved()); - assertEquals(TEST_FUNCTION_P.getSignature(), registry.getSignature(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); - - registry.remove(TEST_FUNCTION_P.getSignature()); - registry.remove(TEST_FUNCTION_OP.getSignature()); - } - -} diff --git a/src/test/java/org/skriptlang/skript/common/function/DefaultFunctionTest.java b/src/test/java/org/skriptlang/skript/common/function/DefaultFunctionTest.java index d98b644ffed..73d471b7c81 100644 --- a/src/test/java/org/skriptlang/skript/common/function/DefaultFunctionTest.java +++ b/src/test/java/org/skriptlang/skript/common/function/DefaultFunctionTest.java @@ -2,14 +2,11 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.lang.function.FunctionEvent; import ch.njol.skript.lang.function.Signature; import ch.njol.skript.registrations.Classes; import ch.njol.util.StringUtils; import org.junit.Test; import org.skriptlang.skript.addon.SkriptAddon; -import org.skriptlang.skript.common.function.DefaultFunction; -import org.skriptlang.skript.common.function.Parameter; import org.skriptlang.skript.common.function.Parameter.Modifier; import static org.junit.Assert.*; @@ -24,11 +21,11 @@ public void testStrings() { .description() .since() .keywords() - .parameter("x", String[].class, Modifier.OPTIONAL) + .parameter("x", String[].class, new Modifier.Optional()) .build(args -> { - String[] xes = args.getOrDefault("x", new String[]{""}); + String[] xs = args.getOrDefault("x", new String[]{""}); - return StringUtils.join(xes, ","); + return StringUtils.join(xs, ","); }); Signature signature = (Signature) built.signature(); @@ -51,7 +48,7 @@ public void testObjectArrays() { .description("x", "y") .since("1", "2") .keywords("x", "y") - .parameter("x", Object[].class, Modifier.OPTIONAL) + .parameter("x", Object[].class, new Modifier.Optional()) .parameter("y", Boolean.class) .build(args -> new Object[]{true, 1}); diff --git a/src/test/java/org/skriptlang/skript/common/function/FunctionArgumentParserTest.java b/src/test/java/org/skriptlang/skript/common/function/FunctionArgumentParserTest.java index b4e6e824367..c5f7932441b 100644 --- a/src/test/java/org/skriptlang/skript/common/function/FunctionArgumentParserTest.java +++ b/src/test/java/org/skriptlang/skript/common/function/FunctionArgumentParserTest.java @@ -1,7 +1,6 @@ package org.skriptlang.skript.common.function; import org.junit.Test; -import org.skriptlang.skript.common.function.FunctionArgumentParser; import org.skriptlang.skript.common.function.FunctionReference.Argument; import org.skriptlang.skript.common.function.FunctionReference.ArgumentType; diff --git a/src/test/java/org/skriptlang/skript/common/function/FunctionRegistryTest.java b/src/test/java/org/skriptlang/skript/common/function/FunctionRegistryTest.java new file mode 100644 index 00000000000..f3702f3ba69 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/common/function/FunctionRegistryTest.java @@ -0,0 +1,426 @@ +package org.skriptlang.skript.common.function; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.lang.function.Signature; +import ch.njol.skript.lang.function.SimpleJavaFunction; +import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.skript.registrations.DefaultClasses; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; +import org.skriptlang.skript.common.function.FunctionRegistry.RetrievalResult; +import org.skriptlang.skript.common.function.FunctionRegistryImpl.FunctionIdentifier; + +import static org.junit.Assert.*; + +public class FunctionRegistryTest { + + private static final FunctionRegistry registry = FunctionRegistry.empty(Skript.instance()); + private static final String FUNCTION_NAME = "testFunctionRegistry"; + private static final String TEST_SCRIPT = "test"; + + private static final Function TEST_FUNCTION = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME, Boolean.class) + .build(b -> true); + + @Test + public void testGetFunctionRetrieval() { + assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + + assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertNull(registry.getSignature(FUNCTION_NAME).retrieved()); + assertTrue(registry.getSignature(FUNCTION_NAME).conflictingArgs().isEmpty()); + + assertEquals(RetrievalResult.NOT_REGISTERED, registry.getFunction(FUNCTION_NAME).result()); + assertNull(registry.getFunction(FUNCTION_NAME).retrieved()); + assertTrue(registry.getFunction(FUNCTION_NAME).conflictingArgs().isEmpty()); + + registry.register(TEST_FUNCTION); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + + assertEquals(RetrievalResult.EXACT, registry.getSignature(FUNCTION_NAME).result()); + assertEquals(TEST_FUNCTION.signature(), registry.getSignature(FUNCTION_NAME).retrieved()); + assertTrue(registry.getSignature(FUNCTION_NAME).conflictingArgs().isEmpty()); + + assertEquals(RetrievalResult.EXACT, registry.getFunction(FUNCTION_NAME).result()); + assertEquals(TEST_FUNCTION, registry.getFunction(FUNCTION_NAME).retrieved()); + assertTrue(registry.getFunction(FUNCTION_NAME).conflictingArgs().isEmpty()); + + registry.remove(TEST_FUNCTION.signature()); + } + + @Test + public void testSimpleMultipleRegistrationsFunction() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertNull(registry.getSignature(FUNCTION_NAME).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.register(TEST_FUNCTION); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertEquals(TEST_FUNCTION.signature(), registry.getSignature(FUNCTION_NAME).retrieved()); + assertEquals(TEST_FUNCTION, registry.getFunction(FUNCTION_NAME).retrieved()); + + assertThrows(SkriptAPIException.class, () -> registry.register(TEST_FUNCTION)); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertEquals(TEST_FUNCTION.signature(), registry.getSignature(FUNCTION_NAME).retrieved()); + assertEquals(TEST_FUNCTION, registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.remove(TEST_FUNCTION.signature()); + } + + @Test + public void testSimpleRegisterRemoveRegisterGlobal() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertNull(registry.getSignature(FUNCTION_NAME).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.register(TEST_FUNCTION); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertEquals(TEST_FUNCTION.signature(), registry.getSignature(FUNCTION_NAME).retrieved()); + assertEquals(TEST_FUNCTION, registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.remove(TEST_FUNCTION.signature()); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertNull(registry.getSignature(FUNCTION_NAME).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.register(TEST_FUNCTION); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertEquals(TEST_FUNCTION.signature(), registry.getSignature(FUNCTION_NAME).retrieved()); + assertEquals(TEST_FUNCTION, registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.remove(TEST_FUNCTION.signature()); + } + + @Test + public void testSimpleRegisterRemoveRegisterLocal() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertNull(registry.getSignature(FUNCTION_NAME).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertEquals(LOCAL_TEST_FUNCTION.signature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).retrieved()); + assertEquals(LOCAL_TEST_FUNCTION, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME).retrieved()); + + registry.remove(LOCAL_TEST_FUNCTION.signature()); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).retrieved()); + assertNull(registry.getSignature(FUNCTION_NAME).retrieved()); + assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION); + registry.register(TEST_FUNCTION); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); + assertEquals(LOCAL_TEST_FUNCTION.signature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).retrieved()); + assertEquals(LOCAL_TEST_FUNCTION, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME).retrieved()); + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + assertEquals(TEST_FUNCTION.signature(), registry.getSignature(FUNCTION_NAME).retrieved()); + assertEquals(TEST_FUNCTION, registry.getFunction(FUNCTION_NAME).retrieved()); + + registry.remove(LOCAL_TEST_FUNCTION.signature()); + registry.remove(TEST_FUNCTION.signature()); + } + + private static final Function TEST_FUNCTION_B = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME, Boolean.class) + .parameter("a", Boolean.class) + .build(b -> true); + + private static final Function TEST_FUNCTION_N = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME, Boolean.class) + .parameter("a", Number.class) + .build(b -> true); + + @Test + public void testMultipleRegistrations() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.register(TEST_FUNCTION_B); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertEquals(TEST_FUNCTION_B.signature(), registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertEquals(TEST_FUNCTION_B, registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.register(TEST_FUNCTION_N); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertEquals(TEST_FUNCTION_B.signature(), registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertEquals(TEST_FUNCTION_B, registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertEquals(TEST_FUNCTION_N.signature(), registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertEquals(TEST_FUNCTION_N, registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + assertThrows(SkriptAPIException.class, () -> registry.register(TEST_FUNCTION_B)); + assertThrows(SkriptAPIException.class, () -> registry.register(TEST_FUNCTION_N)); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertEquals(TEST_FUNCTION_B.signature(), registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertEquals(TEST_FUNCTION_B, registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertEquals(TEST_FUNCTION_N.signature(), registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertEquals(TEST_FUNCTION_N, registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.remove(TEST_FUNCTION_B.signature()); + registry.remove(TEST_FUNCTION_N.signature()); + } + + @Test + public void testRegisterRemoveRegisterGlobal() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.register(TEST_FUNCTION_B); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertEquals(TEST_FUNCTION_B.signature(), registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertEquals(TEST_FUNCTION_B, registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.remove(TEST_FUNCTION_B.signature()); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.register(TEST_FUNCTION_N); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertEquals(TEST_FUNCTION_N.signature(), registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertEquals(TEST_FUNCTION_N, registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.remove(TEST_FUNCTION_N.signature()); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Number.class).retrieved()); + + registry.remove(TEST_FUNCTION_B.signature()); + registry.remove(TEST_FUNCTION_N.signature()); + } + + private static final Function LOCAL_TEST_FUNCTION_B = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME) + .modifiers(new org.skriptlang.skript.common.function.Signature.Modifier.Local(TEST_SCRIPT)) + .parameter("a", Boolean.class) + .build(b -> {}); + + private static final Function LOCAL_TEST_FUNCTION_N = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME) + .modifiers(new org.skriptlang.skript.common.function.Signature.Modifier.Local(TEST_SCRIPT)) + .parameter("a", Number.class) + .build(b -> {}); + + @Test + public void testRegisterRemoveRegisterLocal() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + + registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION_B); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); + assertEquals(LOCAL_TEST_FUNCTION_B.signature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertEquals(LOCAL_TEST_FUNCTION_B, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + + registry.remove(LOCAL_TEST_FUNCTION_B.signature()); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + + registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION_N); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Number.class).result()); + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); + assertEquals(LOCAL_TEST_FUNCTION_N.signature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + assertEquals(LOCAL_TEST_FUNCTION_N, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + + registry.remove(LOCAL_TEST_FUNCTION_N.signature()); + + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).result()); + assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Boolean.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); + assertNull(registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + assertNull(registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + + registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION_N); + registry.register(TEST_FUNCTION_B); + + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).result()); + assertEquals(LOCAL_TEST_FUNCTION_N.signature(), registry.getSignature(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + assertEquals(LOCAL_TEST_FUNCTION_N, registry.getFunction(TEST_SCRIPT, FUNCTION_NAME, Number.class).retrieved()); + assertNotSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Boolean.class).result()); + assertEquals(TEST_FUNCTION_B.signature(), registry.getSignature(FUNCTION_NAME, Boolean.class).retrieved()); + assertEquals(TEST_FUNCTION_B, registry.getFunction(FUNCTION_NAME, Boolean.class).retrieved()); + + registry.remove(LOCAL_TEST_FUNCTION_N.signature()); + registry.remove(TEST_FUNCTION_B.signature()); + } + + @Test + public void testIdentifierEmptyOf() { + FunctionIdentifier identifier = FunctionIdentifier.of(FUNCTION_NAME, true); + + assertEquals(FUNCTION_NAME, identifier.name()); + assertTrue(identifier.local()); + assertEquals(0, identifier.minArgCount()); + assertArrayEquals(new Class[0], identifier.args()); + + assertEquals(FunctionIdentifier.of(FUNCTION_NAME, true), identifier); + } + + @Test + public void testIdentifierOf() { + FunctionIdentifier identifier = FunctionIdentifier.of(FUNCTION_NAME, true, Boolean.class, Number.class); + + assertEquals(FUNCTION_NAME, identifier.name()); + assertTrue(identifier.local()); + assertEquals(2, identifier.minArgCount()); + assertArrayEquals(new Class[]{Boolean.class, Number.class}, identifier.args()); + + assertEquals(FunctionIdentifier.of(FUNCTION_NAME, true, Boolean.class, Number.class), identifier); + } + + @Test + public void testIdentifierSignatureOf() { + SimpleJavaFunction function = new SimpleJavaFunction<>(FUNCTION_NAME, + new ch.njol.skript.lang.function.Parameter[]{ + new ch.njol.skript.lang.function.Parameter<>("a", DefaultClasses.BOOLEAN, true, null), + new ch.njol.skript.lang.function.Parameter<>("b", DefaultClasses.NUMBER, false, new SimpleLiteral(1, true)) + }, DefaultClasses.BOOLEAN, true) { + @Override + public Boolean @Nullable [] executeSimple(Object[][] params) { + return new Boolean[]{true}; + } + }; + + FunctionIdentifier identifier = FunctionIdentifier.of(function.signature()); + + assertEquals(FUNCTION_NAME, identifier.name()); + assertFalse(identifier.local()); + assertEquals(1, identifier.minArgCount()); + assertArrayEquals(new Class[]{Boolean.class, Number[].class}, identifier.args()); + + SimpleJavaFunction function2 = new SimpleJavaFunction<>(FUNCTION_NAME, + new ch.njol.skript.lang.function.Parameter[]{ + new ch.njol.skript.lang.function.Parameter<>("a", DefaultClasses.BOOLEAN, true, null), + new ch.njol.skript.lang.function.Parameter<>("b", DefaultClasses.NUMBER, false, null) + }, DefaultClasses.BOOLEAN, true) { + @Override + public Boolean @Nullable [] executeSimple(Object[][] params) { + return new Boolean[]{true}; + } + }; + + assertEquals(FunctionIdentifier.of(function2.signature()), identifier); + } + + private static final Function LOCAL_TEST_FUNCTION = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME, Boolean.class) + .modifiers(new org.skriptlang.skript.common.function.Signature.Modifier.Local(TEST_SCRIPT)) + .build(b -> true); + + // see https://github.com/SkriptLang/Skript/pull/8015 + @Test + public void testRemoveGlobalScriptFunctions8015() { + // create empty TEST_SCRIPT namespace such that it is not null + registry.register(TEST_SCRIPT, LOCAL_TEST_FUNCTION); + registry.remove(LOCAL_TEST_FUNCTION.signature()); + + assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(TEST_SCRIPT, FUNCTION_NAME).result()); + + // construct a global function with a non-null script, which happens in script functions + ch.njol.skript.lang.function.Signature signature = new Signature<>(TEST_SCRIPT, FUNCTION_NAME, new ch.njol.skript.lang.function.Parameter[0], + false, DefaultClasses.BOOLEAN, true, ""); + SimpleJavaFunction fn = new SimpleJavaFunction<>(signature) { + @Override + public Boolean @Nullable [] executeSimple(Object[][] params) { + return new Boolean[]{true}; + } + }; + + // ensure new behavior + assertThrows(IllegalArgumentException.class, () -> registry.register(TEST_SCRIPT, fn)); + + registry.register(fn); + + assertEquals(RetrievalResult.EXACT, registry.getSignature(FUNCTION_NAME).result()); + + registry.remove(signature); + + assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME).result()); + } + + private static final Function TEST_FUNCTION_P = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME, Boolean.class) + .parameter("a", Player.class) + .build(b -> true); + + private static final Function TEST_FUNCTION_OP = DefaultFunction.builder(Skript.instance(), FUNCTION_NAME, Boolean.class) + .parameter("a", OfflinePlayer.class) + .build(b -> true); + + @Test + public void testGetExactSignature() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, Player.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, Player.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, Player.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(FUNCTION_NAME, OfflinePlayer.class).result()); + assertNull(registry.getSignature(FUNCTION_NAME, OfflinePlayer.class).retrieved()); + assertNull(registry.getFunction(FUNCTION_NAME, OfflinePlayer.class).retrieved()); + + registry.register(TEST_FUNCTION_P); + + assertSame(RetrievalResult.EXACT, registry.getExactSignature(FUNCTION_NAME, Player.class).result()); + assertEquals(TEST_FUNCTION_P.signature(), registry.getExactSignature(FUNCTION_NAME, Player.class).retrieved()); + assertNull(registry.getExactSignature(FUNCTION_NAME, OfflinePlayer.class).retrieved()); + + assertEquals(TEST_FUNCTION_P.signature(), registry.getSignature(FUNCTION_NAME, Player.class).retrieved()); + assertEquals(TEST_FUNCTION_P.signature(), registry.getSignature(FUNCTION_NAME, OfflinePlayer.class).retrieved()); + + registry.remove(TEST_FUNCTION_P.signature()); + registry.remove(TEST_FUNCTION_OP.signature()); + } + +} diff --git a/src/test/java/org/skriptlang/skript/common/function/ParameterTest.java b/src/test/java/org/skriptlang/skript/common/function/ParameterTest.java new file mode 100644 index 00000000000..a2c577ed429 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/common/function/ParameterTest.java @@ -0,0 +1,19 @@ +package org.skriptlang.skript.common.function; + +import org.junit.Test; +import org.skriptlang.skript.common.function.DefaultFunctionImpl.DefaultParameter; +import org.skriptlang.skript.common.function.Parameter.Modifier; + +import static org.junit.Assert.assertEquals; + +public class ParameterTest { + + @Test + public void testToFormattedString() { + DefaultParameter param = new DefaultParameter<>("x", Number.class, + new Modifier.Optional(), new Modifier.Keyed(), new Modifier.Ranged<>(0, 10)); + + assertEquals("x: optional keyed number between 0 and 10", param.toFormattedString()); + } + +} diff --git a/src/test/java/org/skriptlang/skript/util/PriorityTest.java b/src/test/java/org/skriptlang/skript/util/PriorityTest.java index 91ff270458d..6201bc809fd 100644 --- a/src/test/java/org/skriptlang/skript/util/PriorityTest.java +++ b/src/test/java/org/skriptlang/skript/util/PriorityTest.java @@ -24,8 +24,8 @@ public void testBefore() { assertTrue(before.before().contains(base)); assertTrue(before.after().isEmpty()); - assertTrue(before.compareTo(base) < 0); - assertTrue(base.compareTo(before) > 0); + assertTrue(before.isBefore(base)); + assertTrue(base.isAfter(before)); // Different instances, but functionally equal assertEquals(before, Priority.before(base)); @@ -38,8 +38,8 @@ public void testAfter() { assertTrue(after.before().isEmpty()); assertTrue(after.after().contains(base)); - assertTrue(after.compareTo(base) > 0); - assertTrue(base.compareTo(after) < 0); + assertTrue(after.isAfter(base)); + assertTrue(base.isBefore(after)); // Different instances, but functionally equal assertEquals(after, Priority.after(base)); @@ -51,10 +51,8 @@ public void testBoth() { Priority before = Priority.before(base); Priority after = Priority.after(base); - // 'before' should be before 'after' - assertTrue(before.compareTo(after) < 0); - // 'after' should be after 'before' - assertTrue(after.compareTo(before) > 0); + assertTrue(before.isBefore(after)); + assertTrue(after.isAfter(before)); } @Test @@ -63,17 +61,15 @@ public void testComplex() { Priority before = Priority.before(base); Priority afterBefore = Priority.after(before); - // 'afterBefore' should be before 'base' - assertTrue(afterBefore.compareTo(base) < 0); - // 'base' should be after 'afterBefore' - assertTrue(base.compareTo(afterBefore) > 0); + + assertTrue(afterBefore.isBefore(base)); + assertTrue(base.isAfter(afterBefore)); Priority after = Priority.after(base); Priority beforeAfter = Priority.before(after); - // 'beforeAfter' should be after 'base' - assertTrue(beforeAfter.compareTo(base) > 0); - // 'base' should be before 'beforeAfter' - assertTrue(base.compareTo(beforeAfter) < 0); + + assertTrue(beforeAfter.isAfter(base)); + assertTrue(base.isBefore(beforeAfter)); } } diff --git a/src/test/skript/environments/java21/paper-1.21.4.json b/src/test/skript/environments/java21/paper-1.21.4.json deleted file mode 100644 index 5957458a7a9..00000000000 --- a/src/test/skript/environments/java21/paper-1.21.4.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.21.4", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.21.4", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar", "--nogui" - ] -} diff --git a/src/test/skript/tests/bukkit/item/ExprItemModel.sk b/src/test/skript/tests/bukkit/item/ExprItemModel.sk new file mode 100644 index 00000000000..5236e990e07 --- /dev/null +++ b/src/test/skript/tests/bukkit/item/ExprItemModel.sk @@ -0,0 +1,46 @@ +using error catching + +test "ExprItemModel": + set {_item} to diamond + + set item model of {_item} to "emerald" + assert item model of {_item} is "minecraft:emerald" with "Set item model of item" + + set {_inv} to chest inventory with 1 row named "test slots" + set slot 1 of {_inv} to diamond + + set item model of slot 1 of {_inv} to "emerald" + + assert item model of slot 1 of {_inv} is "minecraft:emerald" with "Set item model of slot from container" + + set {_item} to dirt with item model "diamond" + assert item model of {_item} is "minecraft:diamond" with "ItemType with item model" + + reset item model of {_item} + assert item model of {_item} is not set with "Reset item model of item" + + set item model of {_item} to "minecraft:redstone" + delete item model of {_item} + assert item model of {_item} is not set with "Delete item model of item" + + set {_string} to "test_string" + set item model of {_item} to {_string} + assert item model of {_item} is "minecraft:test_string" with "Set item model from variable string" + + set item model of {_item} to {_} + assert item model of {_item} is not set with "Set item model of item to null" + + set {_item} to diamond with model {_} + assert item model of {_item} is not set with "Item with null item model" + + set {_item} to emerald with model {_string} + assert item model of {_item} is "minecraft:test_string" with "Item with item model of variable string" + + set {_item} to diamond + catch runtime errors: + set item model of {_item} to "with spaces" + assert last caught runtime errors are set with "Runtime errors produced for invalid key for item model" + + catch runtime errors: + set {_item} to diamond with model "with spaces" + assert last caught runtime errors are set with "Runtime errors produced for invalid key for item with model"