+ *
+ * @param The return type.
+ * @see #builder(SkriptAddon, String, Class)
+ */
+public final class DefaultFunction extends ch.njol.skript.lang.function.Function {
+
+ /**
+ * Creates a new builder for a function.
+ *
+ * @param name The name of the function.
+ * @param returnType The type of the function.
+ * @param The return type.
+ * @return The builder for a function.
+ */
+ public static Builder builder(@NotNull SkriptAddon source, @NotNull String name, @NotNull Class returnType) {
+ return new Builder<>(source, name, returnType);
+ }
+
+ private final Parameter>[] parameters;
+ private final Function execute;
+
+ private final String[] description;
+ private final String[] since;
+ private final String[] examples;
+ private final String[] keywords;
+
+ private DefaultFunction(
+ String name, Parameter>[] parameters,
+ Class returnType, boolean single,
+ @Nullable ch.njol.skript.util.Contract contract, Function execute,
+ String[] description, String[] since, String[] examples, String[] keywords
+ ) {
+ super(new Signature<>(null, name, parameters, returnType, single, contract));
+
+ 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.parameters = parameters;
+ this.execute = execute;
+ this.description = description;
+ this.since = since;
+ this.examples = examples;
+ this.keywords = keywords;
+ }
+
+ @Override
+ public T @Nullable [] execute(FunctionEvent> event, Object[][] params) {
+ Map args = new LinkedHashMap<>();
+
+ int length = Math.min(parameters.length, params.length);
+ for (int i = 0; i < length; i++) {
+ Object[] arg = params[i];
+ org.skriptlang.skript.lang.function.Parameter> parameter = parameters[i];
+
+ if (arg == null || arg.length == 0) {
+ if (parameter.modifiers().contains(Modifier.OPTIONAL)) {
+ continue;
+ } else {
+ return null;
+ }
+ }
+
+ if (arg.length == 1 || parameter.single()) {
+ assert parameter.type().isAssignableFrom(arg[0].getClass())
+ : "argument type %s does not match parameter type %s".formatted(parameter.type().getSimpleName(),
+ arg[0].getClass().getSimpleName());
+
+ args.put(parameter.name(), arg[0]);
+ } else {
+ assert parameter.type().isAssignableFrom(arg.getClass())
+ : "argument type %s does not match parameter type %s".formatted(parameter.type().getSimpleName(),
+ arg.getClass().getSimpleName());
+
+ args.put(parameter.name(), arg);
+ }
+ }
+
+ FunctionArguments arguments = new FunctionArguments(args);
+ T result = execute.apply(arguments);
+
+ if (result == null) {
+ return null;
+ } else if (result.getClass().isArray()) {
+ //noinspection unchecked
+ return (T[]) result;
+ } else {
+ //noinspection unchecked
+ T[] array = (T[]) Array.newInstance(result.getClass(), 1);
+ array[0] = result;
+ return array;
+ }
+ }
+
+ @Override
+ public boolean resetReturnValue() {
+ return true;
+ }
+
+ /**
+ * Returns this function's description.
+ *
+ * @return The description.
+ */
+ public @NotNull String @NotNull [] description() {
+ return description;
+ }
+
+ /**
+ * Returns this function's version history.
+ *
+ * @return The version history.
+ */
+ public @NotNull String @NotNull [] since() {
+ return since;
+ }
+
+ /**
+ * Returns this function's examples.
+ *
+ * @return The examples.
+ */
+ public @NotNull String @NotNull [] examples() {
+ return examples;
+ }
+
+ /**
+ * Returns this function's keywords.
+ *
+ * @return The keywords.
+ */
+ public @NotNull String @NotNull [] keywords() {
+ return keywords;
+ }
+
+ /**
+ * Registers this function.
+ *
+ * @return This function.
+ */
+ @Contract(" -> this")
+ public DefaultFunction register() {
+ Functions.register(this);
+
+ return this;
+ }
+
+ public static class Builder {
+
+ private final SkriptAddon source;
+ private final String name;
+ private final Class returnType;
+ private final Map> parameters = new LinkedHashMap<>();
+
+ private ch.njol.skript.util.Contract contract = null;
+
+ private String[] description, since, examples, keywords;
+
+ private Builder(@NotNull SkriptAddon source, @NotNull String name, @NotNull Class returnType) {
+ Preconditions.checkNotNull(source, "source cannot be null");
+ Preconditions.checkNotNull(name, "name cannot be null");
+ Preconditions.checkNotNull(returnType, "return type cannot be null");
+
+ this.source = source;
+ this.name = name;
+ this.returnType = returnType;
+ }
+
+ /**
+ * Sets this function builder's {@link Contract}.
+ *
+ * @param contract The contract.
+ * @return This builder.
+ */
+ @Contract("_ -> this")
+ public Builder contract(@NotNull ch.njol.skript.util.Contract contract) {
+ Preconditions.checkNotNull(contract, "contract cannot be null");
+
+ this.contract = contract;
+ return this;
+ }
+
+ /**
+ * Sets this function builder's description.
+ *
+ * @param description The description.
+ * @return This builder.
+ */
+ @Contract("_ -> this")
+ public Builder description(@NotNull String @NotNull ... description) {
+ Preconditions.checkNotNull(description, "description cannot be null");
+ checkNotNull(description, "description contents cannot be null");
+
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * Sets this function builder's version history.
+ *
+ * @param since The version information.
+ * @return This builder.
+ */
+ @Contract("_ -> this")
+ public Builder since(@NotNull String @NotNull ... since) {
+ Preconditions.checkNotNull(since, "since cannot be null");
+ checkNotNull(since, "since contents cannot be null");
+
+ this.since = since;
+ return this;
+ }
+
+ /**
+ * Sets this function builder's examples.
+ *
+ * @param examples The examples.
+ * @return This builder.
+ */
+ @Contract("_ -> this")
+ public Builder examples(@NotNull String @NotNull ... examples) {
+ Preconditions.checkNotNull(examples, "examples cannot be null");
+ checkNotNull(examples, "examples contents cannot be null");
+
+ this.examples = examples;
+ return this;
+ }
+
+ /**
+ * Sets this function builder's keywords.
+ *
+ * @param keywords The keywords.
+ * @return This builder.
+ */
+ @Contract("_ -> this")
+ public Builder keywords(@NotNull String @NotNull ... keywords) {
+ Preconditions.checkNotNull(keywords, "keywords cannot be null");
+ checkNotNull(keywords, "keywords contents cannot be null");
+
+ this.keywords = keywords;
+ return this;
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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")
+ public Builder parameter(@NotNull String name, @NotNull Class> type, 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;
+ }
+
+ /**
+ * Completes this builder with the code to execute on call of this function.
+ *
+ * @param execute The code to execute.
+ * @return The final function.
+ */
+ public DefaultFunction build(@NotNull Function execute) {
+ Preconditions.checkNotNull(execute, "execute cannot be null");
+
+ return new DefaultFunction<>(name, parameters.values().toArray(new Parameter[0]), returnType,
+ !returnType.isArray(), contract, execute, description, since, examples, keywords);
+ }
+
+ }
+
+ /**
+ * Returns the {@link ClassInfo} of the non-array type of {@code cls}.
+ *
+ * @param cls The class.
+ * @param The type of class.
+ * @return The non-array {@link ClassInfo} of {@code cls}.
+ */
+ static ClassInfo getClassInfo(Class cls) {
+ ClassInfo classInfo;
+ if (cls.isArray()) {
+ //noinspection unchecked
+ classInfo = (ClassInfo) Classes.getExactClassInfo(cls.componentType());
+ } else {
+ classInfo = Classes.getExactClassInfo(cls);
+ }
+ if (classInfo == null) {
+ throw new IllegalArgumentException("No type found for " + cls.getSimpleName());
+ }
+ return classInfo;
+ }
+
+}
diff --git a/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java b/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java
index 00dd6b5bb59..4fc4d553c2f 100644
--- a/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java
+++ b/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java
@@ -176,7 +176,7 @@ else if (parameters.length < signature.getMinParameters())
for (int i = 0; i < parameters.length; i++) {
Parameter> parameter = signature.parameters[varArgs ? 0 : i];
//noinspection unchecked
- Expression> expression = parameters[i].getConvertedExpression(parameter.type.getC());
+ Expression> expression = parameters[i].getConvertedExpression(parameter.type());
if (expression == null) {
return null;
} else if (parameter.single && !expression.isSingle()) {
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 37301f2b03f..43cedf37be9 100644
--- a/src/main/java/ch/njol/skript/lang/function/Function.java
+++ b/src/main/java/ch/njol/skript/lang/function/Function.java
@@ -8,6 +8,8 @@
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.addon.SkriptAddon;
+import org.skriptlang.skript.lang.function.Parameter.Modifier;
import java.util.Arrays;
@@ -89,11 +91,11 @@ public boolean isSingle() {
// Execute parameters or default value expressions
for (int i = 0; i < parameters.length; i++) {
Parameter> parameter = parameters[i];
- Object[] parameterValue = parameter.keyed ? convertToKeyed(parameterValues[i]) : parameterValues[i];
- if (parameterValue == null) { // Go for default value
+ Object[] parameterValue = parameter.modifiers().contains(Modifier.KEYED) ? convertToKeyed(parameterValues[i]) : parameterValues[i];
+ if (!(this instanceof DefaultFunction) && parameterValue == null) { // Go for default value
assert parameter.def != null; // Should've been parse error
Object[] defaultValue = parameter.def.getArray(event);
- if (parameter.keyed && KeyProviderExpression.areKeysRecommended(parameter.def)) {
+ if (parameter.modifiers().contains(Modifier.KEYED) && KeyProviderExpression.areKeysRecommended(parameter.def)) {
String[] keys = ((KeyProviderExpression>) parameter.def).getArrayKeys(event);
parameterValue = KeyedValue.zip(defaultValue, keys);
} else {
@@ -107,7 +109,7 @@ public boolean isSingle() {
* really have a concept of nulls, it was changed. The config
* option may be removed in future.
*/
- if (!executeWithNulls && parameterValue.length == 0)
+ if (!(this instanceof DefaultFunction) && !executeWithNulls && parameterValue.length == 0)
return null;
parameterValues[i] = parameterValue;
}
diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionArguments.java b/src/main/java/ch/njol/skript/lang/function/FunctionArguments.java
new file mode 100644
index 00000000000..48afb05356b
--- /dev/null
+++ b/src/main/java/ch/njol/skript/lang/function/FunctionArguments.java
@@ -0,0 +1,113 @@
+package ch.njol.skript.lang.function;
+
+import com.google.common.base.Preconditions;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Unmodifiable;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * A class containing all arguments in a function call.
+ */
+public final class FunctionArguments {
+
+ private final @Unmodifiable @NotNull Map arguments;
+
+ public FunctionArguments(@NotNull Map arguments) {
+ Preconditions.checkNotNull(arguments, "arguments cannot be null");
+
+ this.arguments = Collections.unmodifiableMap(arguments);
+ }
+
+ /**
+ * Gets a specific argument by name.
+ *
+ * This method automatically conforms to your expected type,
+ * to avoid having to cast from Object. Use this method as follows.
+ *
+ * Number value = args.get("n");
+ * Boolean value = args.get("b");
+ * Number[] value = args.get("ns");
+ * args.get("b"); // inline
+ *
+ *
+ *
+ * @param name The name of the parameter.
+ * @param The type to return.
+ * @return The value present, or null if no value is present.
+ */
+ public T get(@NotNull String name) {
+ Preconditions.checkNotNull(name, "name cannot be null");
+
+ //noinspection unchecked
+ return (T) arguments.get(name);
+ }
+
+ /**
+ * Gets a specific argument by name, or a default value if no value is found.
+ *
+ * This method automatically conforms to your expected type,
+ * to avoid having to cast from Object. Use this method as follows.
+ *
+ * Number value = args.getOrDefault("n", 3.0);
+ * boolean value = args.getOrDefault("b", false);
+ * args.getOrDefault("b", () -> false); // inline
+ *
+ *
+ *
+ * @param name The name of the parameter.
+ * @param defaultValue The default value.
+ * @param The type to return.
+ * @return The value present, or the default value if no value is present.
+ */
+ public T getOrDefault(@NotNull String name, T defaultValue) {
+ Preconditions.checkNotNull(name, "name cannot be null");
+
+ //noinspection unchecked
+ return (T) arguments.getOrDefault(name, defaultValue);
+ }
+
+ /**
+ * Gets a specific argument by name, or calculates the default value if no value is found.
+ *
+ * This method automatically conforms to your expected type,
+ * to avoid having to cast from Object. Use this method as follows.
+ *
+ * Number value = args.getOrDefault("n", () -> 3.0);
+ * boolean value = args.getOrDefault("b", () -> false);
+ * args.getOrDefault("b", () -> false); // inline
+ *
+ *
+ *
+ * @param name The name of the parameter.
+ * @param defaultValue A supplier that calculates the default value if no existing value is found.
+ * @param The type to return.
+ * @return The value present, or the calculated default value if no value is present.
+ */
+ public T getOrDefault(@NotNull String name, Supplier defaultValue) {
+ Preconditions.checkNotNull(name, "name cannot be null");
+
+ Object existing = arguments.get(name);
+ if (existing == null) {
+ return defaultValue.get();
+ } else {
+ //noinspection unchecked
+ return (T) existing;
+ }
+ }
+
+ /**
+ * Returns whether this method call contained the following argument.
+ *
+ * @param name The argument.
+ * @return True if the argument is present.
+ */
+ public boolean has(@NotNull String name) {
+ Preconditions.checkNotNull(name, "name cannot be null");
+
+ return arguments.containsKey(name);
+ }
+
+}
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 7cc7fdfc745..3fc4a368692 100644
--- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java
+++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java
@@ -19,6 +19,7 @@
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.lang.converter.Converters;
+import org.skriptlang.skript.lang.function.Parameter.Modifier;
import org.skriptlang.skript.util.Executable;
import java.util.*;
@@ -234,13 +235,15 @@ public boolean validateFunction(boolean first) {
RetainingLogHandler log = SkriptLogger.startRetainingLog();
try {
//noinspection unchecked
- Expression> e = parameters[i].getConvertedExpression(p.type.getC());
+ Expression> e = parameters[i].getConvertedExpression(p.type());
if (e == null) {
if (first) {
if (LiteralUtils.hasUnparsedLiteral(parameters[i])) {
Skript.error("Can't understand this expression: " + parameters[i].toString());
} else {
- Skript.error("The " + StringUtils.fancyOrderNumber(i + 1) + " argument given to the function '" + stringified + "' is not of the required type " + p.type + "."
+ String type = Classes.toString(DefaultFunction.getClassInfo(p.type()));
+
+ Skript.error("The " + StringUtils.fancyOrderNumber(i + 1) + " argument given to the function '" + stringified + "' is not of the required type " + type + "."
+ " Check the correct order of the arguments and put lists into parentheses if appropriate (e.g. 'give(player, (iron ore and gold ore))')."
+ " Please note that storing the value in a variable and then using that variable as parameter may suppress this error, but it still won't work.");
}
@@ -292,7 +295,7 @@ private void parseParameters() {
}
/**
- * Attempts to get this function's signature.
+ * Attempts to geoopst this function's signature.
*/
private Signature> getRegisteredSignature() {
parseParameters();
@@ -368,10 +371,10 @@ public boolean resetReturnValue() {
// Prepare parameter values for calling
Object[][] params = new Object[singleListParam ? 1 : parameters.length][];
if (singleListParam && parameters.length > 1) { // All parameters to one list
- params[0] = evaluateSingleListParameter(parameters, event, function.getParameter(0).keyed);
+ params[0] = evaluateSingleListParameter(parameters, event, function.getParameter(0).modifiers().contains(Modifier.KEYED));
} else { // Use parameters in normal way
for (int i = 0; i < parameters.length; i++)
- params[i] = evaluateParameter(parameters[i], event, function.getParameter(i).keyed);
+ params[i] = evaluateParameter(parameters[i], event, function.getParameter(i).modifiers().contains(Modifier.KEYED));
}
// Execute the function
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 0274257cea5..1ab10a1df35 100644
--- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java
+++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java
@@ -631,11 +631,11 @@ static FunctionIdentifier of(@NotNull Signature> signature) {
int optionalArgs = 0;
for (int i = 0; i < signatureParams.length; i++) {
Parameter> param = signatureParams[i];
- if (param.def != null) {
+ if (param.isOptional()) {
optionalArgs++;
}
- Class> type = param.getType().getC();
+ Class> type = param.type();
if (param.isSingleValue()) {
parameters[i] = type;
} else {
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 909ae8127da..68300c85a07 100644
--- a/src/main/java/ch/njol/skript/lang/function/Functions.java
+++ b/src/main/java/ch/njol/skript/lang/function/Functions.java
@@ -53,11 +53,33 @@ private Functions() {}
static boolean callFunctionEvents = false;
+
/**
- * Registers a function written in Java.
+ * Registers a {@link DefaultFunction}.
*
- * @return The passed function
+ * @param function The function to register.
+ * @return The registered function.
+ */
+ static DefaultFunction> register(DefaultFunction> function) {
+ Skript.checkAcceptRegistrations();
+
+ String name = function.getName();
+ if (!name.matches(functionNamePattern))
+ throw new SkriptAPIException("Invalid function name '%s'".formatted(name));
+
+ javaNamespace.addSignature(function.getSignature());
+ javaNamespace.addFunction(function);
+ globalFunctions.put(function.getName(), javaNamespace);
+
+ FunctionRegistry.getRegistry().register(null, function);
+
+ return function;
+ }
+
+ /**
+ * @deprecated Use {@link DefaultFunction#register()} or {@link #register(DefaultFunction)} instead.
*/
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public static JavaFunction> registerFunction(JavaFunction> function) {
Skript.checkAcceptRegistrations();
String name = function.getName();
@@ -161,11 +183,11 @@ public static JavaFunction> registerFunction(JavaFunction> function) {
Parameter>[] parameters = signature.parameters;
if (parameters.length == 1 && !parameters[0].isSingleValue()) {
- existing = FunctionRegistry.getRegistry().getSignature(signature.script, signature.getName(), parameters[0].type.getC().arrayType());
+ existing = FunctionRegistry.getRegistry().getSignature(signature.script, signature.getName(), parameters[0].type().arrayType());
} else {
Class>[] types = new Class>[parameters.length];
for (int i = 0; i < parameters.length; i++) {
- types[i] = parameters[i].type.getC();
+ types[i] = parameters[i].type();
}
existing = FunctionRegistry.getRegistry().getSignature(signature.script, signature.getName(), types);
@@ -408,12 +430,25 @@ public static void clearFunctions() {
toValidate.clear();
}
+ /**
+ * @deprecated Use {@link #getDefaultFunctions()} instead.
+ */
@SuppressWarnings({"unchecked"})
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public static Collection> getJavaFunctions() {
// We know there are only Java functions in that namespace
return (Collection>) (Object) javaNamespace.getFunctions();
}
+ /**
+ * Returns all functions registered using Java.
+ *
+ * @return All {@link JavaFunction} or {@link DefaultFunction} functions.
+ */
+ public static Collection> getDefaultFunctions() {
+ 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
diff --git a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java
index c7414f19b25..ef4b39e1a3c 100644
--- a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java
+++ b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java
@@ -6,6 +6,10 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+/**
+ * @deprecated Use {@link DefaultFunction} instead.
+ */
+@Deprecated(since = "INSERT VERSION", forRemoval = true)
public abstract class JavaFunction extends Function {
private @NotNull String @Nullable [] returnedKeys;
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 58471540de2..2dfe50eaad7 100644
--- a/src/main/java/ch/njol/skript/lang/function/Parameter.java
+++ b/src/main/java/ch/njol/skript/lang/function/Parameter.java
@@ -14,15 +14,15 @@
import ch.njol.skript.util.Utils;
import ch.njol.util.NonNullPair;
import ch.njol.util.StringUtils;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Unmodifiable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
+import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public final class Parameter {
+public final class Parameter implements org.skriptlang.skript.lang.function.Parameter {
public final static Pattern PARAM_PATTERN = Pattern.compile("^\\s*([^:(){}\",]+?)\\s*:\\s*([a-zA-Z ]+?)\\s*(?:\\s*=\\s*(.+))?\\s*$");
@@ -32,12 +32,12 @@ public final class Parameter {
* If {@link SkriptConfig#caseInsensitiveVariables} is {@code true},
* then the valid variable names may not necessarily match this string in casing.
*/
- final String name;
+ private final String name;
/**
* Type of the parameter.
*/
- final ClassInfo type;
+ private final ClassInfo type;
/**
* Expression that will provide default value of this parameter
@@ -50,30 +50,88 @@ public final class Parameter {
*/
final boolean single;
+ private final Set modifiers;
+
/**
- * Whether this parameter takes in key-value pairs.
- *
- * If this is true, a {@link ch.njol.skript.lang.KeyedValue} array containing key-value pairs will be passed to
- * {@link Function#execute(FunctionEvent, Object[][])} rather than a value-only object array.
+ * @deprecated Use {@link org.skriptlang.skript.lang.function.Parameter}
+ * or {@link ch.njol.skript.lang.function.DefaultFunction.Builder#parameter(String, Class, Modifier...)}
+ * instead.
*/
- final boolean keyed;
-
+ @Deprecated(since = "INSERT VERSION", forRemoval = true)
public Parameter(String name, ClassInfo type, boolean single, @Nullable Expression extends T> def) {
this(name, type, single, def, false);
}
+ /**
+ * @deprecated Use {@link org.skriptlang.skript.lang.function.Parameter}
+ * or {@link ch.njol.skript.lang.function.DefaultFunction.Builder#parameter(String, Class, Modifier...)}
+ * instead.
+ */
+ @Deprecated(since = "INSERT VERSION", forRemoval = true)
public Parameter(String name, ClassInfo type, boolean single, @Nullable Expression extends T> def, boolean keyed) {
this.name = name;
this.type = type;
this.def = def;
this.single = single;
- this.keyed = keyed;
+ this.modifiers = new HashSet<>();
+
+ if (def != null) {
+ modifiers.add(Modifier.OPTIONAL);
+ }
+ if (keyed) {
+ modifiers.add(Modifier.KEYED);
+ }
+ }
+
+ /**
+ * @deprecated Use {@link org.skriptlang.skript.lang.function.Parameter}
+ * or {@link ch.njol.skript.lang.function.DefaultFunction.Builder#parameter(String, Class, Modifier...)}
+ * instead.
+ */
+ @Deprecated(since = "INSERT VERSION", forRemoval = true)
+ public Parameter(String name, ClassInfo type, boolean single, @Nullable Expression extends T> def, boolean keyed, boolean optional) {
+ this.name = name;
+ this.type = type;
+ this.def = def;
+ this.single = single;
+ this.modifiers = new HashSet<>();
+
+ if (optional) {
+ modifiers.add(Modifier.OPTIONAL);
+ }
+ if (keyed) {
+ modifiers.add(Modifier.KEYED);
+ }
+ }
+
+ /**
+ * Constructs a new parameter for script functions.
+ *
+ * @param name The name.
+ * @param type The type of the parameter.
+ * @param single Whether the parameter is single.
+ * @param def The default value.
+ */
+ Parameter(String name, ClassInfo type, boolean single, @Nullable Expression extends T> def, Modifier... modifiers) {
+ this.name = name;
+ this.type = type;
+ this.def = def;
+ this.single = single;
+ this.modifiers = Set.of(modifiers);
}
/**
- * Get the Type of this parameter.
- * @return Type of the parameter
+ * Returns whether this parameter is optional or not.
+ * @return Whether this parameter is optional or not.
*/
+ public boolean isOptional() {
+ return modifiers.contains(Modifier.OPTIONAL);
+ }
+
+ /**
+ * @deprecated Use {@link #type()} instead.
+ */
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public ClassInfo getType() {
return type;
}
@@ -101,7 +159,16 @@ public ClassInfo getType() {
log.stop();
}
}
- return new Parameter<>(name, type, single, d, !single);
+
+ Set modifiers = new HashSet<>();
+ if (d != null) {
+ modifiers.add(Modifier.OPTIONAL);
+ }
+ if (!single) {
+ modifiers.add(Modifier.KEYED);
+ }
+
+ return new Parameter<>(name, type, single, d, modifiers.toArray(new Modifier[0]));
}
/**
@@ -167,10 +234,9 @@ public ClassInfo getType() {
}
/**
- * Get the name of this parameter.
- *
Will be used as name for the local variable that contains value of it inside function.
- * @return Name of this parameter
+ * @deprecated Use {@link #name()} instead.
*/
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public String getName() {
return name;
}
@@ -191,6 +257,19 @@ public boolean isSingleValue() {
return single;
}
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Parameter> parameter)) {
+ return false;
+ }
+
+ return modifiers.equals(parameter.modifiers)
+ && single == parameter.single
+ && name.equals(parameter.name)
+ && type.equals(parameter.type)
+ && Objects.equals(def, parameter.def);
+ }
+
@Override
public String toString() {
return toString(Skript.debug());
@@ -200,4 +279,19 @@ public String toString(boolean debug) {
return name + ": " + Utils.toEnglishPlural(type.getCodeName(), !single) + (def != null ? " = " + def.toString(null, debug) : "");
}
+ @Override
+ public @NotNull String name() {
+ return name;
+ }
+
+ @Override
+ public @NotNull Class type() {
+ return type.getC();
+ }
+
+ @Override
+ public @Unmodifiable @NotNull Set modifiers() {
+ return Collections.unmodifiableSet(modifiers);
+ }
+
}
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 ec493bdbd6c..0ac8328eda4 100644
--- a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java
+++ b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java
@@ -37,11 +37,11 @@ public ScriptFunction(Signature sign, SectionNode node) {
try {
hintManager.enterScope(false);
for (Parameter> parameter : sign.getParameters()) {
- String hintName = parameter.getName();
+ String hintName = parameter.name();
if (!parameter.isSingleValue()) {
hintName += Variable.SEPARATOR + "*";
}
- hintManager.set(hintName, parameter.getType().getC());
+ hintManager.set(hintName, parameter.type());
}
trigger = loadReturnableTrigger(node, "function " + sign.getName(), new SimpleEvent());
} finally {
@@ -60,11 +60,11 @@ public ScriptFunction(Signature sign, SectionNode node) {
Parameter> parameter = parameters[i];
Object[] val = params[i];
if (parameter.single && val.length > 0) {
- Variables.setVariable(parameter.name, val[0], event, true);
+ Variables.setVariable(parameter.name(), val[0], event, true);
} else {
for (Object value : val) {
KeyedValue> keyedValue = (KeyedValue>) value;
- Variables.setVariable(parameter.name + "::" + keyedValue.key(), keyedValue.value(), event, true);
+ Variables.setVariable(parameter.name() + "::" + keyedValue.key(), keyedValue.value(), event, true);
}
}
}
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 cdad3b18cee..202d42a9455 100644
--- a/src/main/java/ch/njol/skript/lang/function/Signature.java
+++ b/src/main/java/ch/njol/skript/lang/function/Signature.java
@@ -5,6 +5,7 @@
import ch.njol.skript.util.Utils;
import ch.njol.skript.util.Contract;
import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.lang.function.Parameter.Modifier;
import java.util.Collection;
import java.util.Collections;
@@ -63,7 +64,7 @@ public class Signature {
*/
final @Nullable Contract contract;
- public Signature(String script,
+ public Signature(@Nullable String script,
String name,
Parameter>[] parameters, boolean local,
@Nullable ClassInfo returnType,
@@ -82,6 +83,45 @@ public Signature(String script,
calls = Collections.newSetFromMap(new WeakHashMap<>());
}
+ /**
+ * Creates a new signature.
+ *
+ * @param script The script of this signature.
+ * @param name The name of the function.
+ * @param parameters The parameters.
+ * @param returnType The return type class.
+ * @param contract A {@link Contract} that may belong to this signature.
+ */
+ public Signature(@Nullable String script,
+ String name,
+ org.skriptlang.skript.lang.function.Parameter>[] parameters,
+ @Nullable Class returnType,
+ boolean single,
+ @Nullable Contract contract) {
+ this.parameters = new Parameter[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ org.skriptlang.skript.lang.function.Parameter> parameter = parameters[i];
+ this.parameters[i] = new Parameter<>(parameter.name(),
+ DefaultFunction.getClassInfo(parameter.type()), parameter.single(),
+ null,
+ parameter.modifiers().toArray(new Modifier[0]));
+ }
+
+ this.script = script;
+ this.name = name;
+ this.local = script != null;
+ if (returnType != null) {
+ this.returnType = DefaultFunction.getClassInfo(returnType);
+ } else {
+ this.returnType = null;
+ }
+ this.single = single;
+ this.contract = contract;
+ this.originClassPath = "";
+
+ calls = Collections.newSetFromMap(new WeakHashMap<>());
+ }
+
public Signature(String script,
String name,
Parameter>[] parameters, boolean local,
@@ -94,7 +134,7 @@ public Signature(String script,
public Signature(String script, String name, Parameter>[] parameters, boolean local, @Nullable ClassInfo returnType, boolean single) {
this(script, name, parameters, local, returnType, single, null);
}
-
+
public String getName() {
return name;
}
@@ -120,6 +160,10 @@ public boolean isSingle() {
return single;
}
+ /**
+ * @deprecated Unused and unsafe.
+ */
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public String getOriginClassPath() {
return originClassPath;
}
@@ -145,7 +189,7 @@ public int getMaxParameters() {
*/
public int getMinParameters() {
for (int i = parameters.length - 1; i >= 0; i--) {
- if (parameters[i].def == null)
+ if (!parameters[i].isOptional())
return i + 1;
}
return 0; // No-args function
diff --git a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java
index b83c911945b..b0fc05e5526 100644
--- a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java
+++ b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java
@@ -7,10 +7,9 @@
import org.jetbrains.annotations.Nullable;
/**
- * A {@link JavaFunction} which doesn't make use of
- * the {@link FunctionEvent} instance and that cannot
- * accept empty / {@code null} parameters.
+ * @deprecated Use {@link DefaultFunction} instead.
*/
+@Deprecated(since = "INSERT VERSION", forRemoval = true)
public abstract class SimpleJavaFunction extends JavaFunction {
public SimpleJavaFunction(Signature sign) {
diff --git a/src/main/java/org/skriptlang/skript/lang/function/DefaultParameter.java b/src/main/java/org/skriptlang/skript/lang/function/DefaultParameter.java
new file mode 100644
index 00000000000..1f535db03e3
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/lang/function/DefaultParameter.java
@@ -0,0 +1,20 @@
+package org.skriptlang.skript.lang.function;
+
+import java.util.Set;
+
+/**
+ * A parameter for a {@link ch.njol.skript.lang.function.DefaultFunction}.
+ *
+ * @param name The name.
+ * @param type The type's class.
+ * @param modifiers The modifiers.
+ * @param The type.
+ */
+public record DefaultParameter(String name, Class type, Set modifiers)
+ implements Parameter {
+
+ public DefaultParameter(String name, Class type, Modifier... modifiers) {
+ this(name, type, Set.of(modifiers));
+ }
+
+}
diff --git a/src/main/java/org/skriptlang/skript/lang/function/Parameter.java b/src/main/java/org/skriptlang/skript/lang/function/Parameter.java
new file mode 100644
index 00000000000..f8a1916b384
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/lang/function/Parameter.java
@@ -0,0 +1,64 @@
+package org.skriptlang.skript.lang.function;
+
+import ch.njol.skript.lang.function.DefaultFunction.Builder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Unmodifiable;
+
+import java.util.Set;
+
+/**
+ * Represents a function parameter.
+ *
+ * @param The type of the function parameter.
+ */
+public interface Parameter {
+
+ /**
+ * @return The name of this parameter.
+ */
+ @NotNull String name();
+
+ /**
+ * @return The type of this parameter.
+ */
+ @NotNull Class type();
+
+ /**
+ * @return All modifiers belonging to this parameter.
+ */
+ @Unmodifiable
+ @NotNull Set modifiers();
+
+ /**
+ * @return Whether this parameter is for single values.
+ */
+ default boolean single() {
+ return !type().isArray();
+ }
+
+ /**
+ * Represents a modifier that can be applied to a parameter
+ * when constructing one using {@link Builder#parameter(String, Class, Modifier[])}}.
+ */
+ interface Modifier {
+
+ /**
+ * @return A new Modifier instance to be used as a custom flag.
+ */
+ static Modifier of() {
+ return new Modifier() { };
+ }
+
+ /**
+ * The modifier for parameters that are optional.
+ */
+ Modifier OPTIONAL = of();
+
+ /**
+ * The modifier for parameters that support optional keyed expressions.
+ */
+ Modifier KEYED = of();
+
+ }
+
+}
diff --git a/src/main/java/org/skriptlang/skript/lang/function/ScriptParameter.java b/src/main/java/org/skriptlang/skript/lang/function/ScriptParameter.java
new file mode 100644
index 00000000000..12c677e0299
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/lang/function/ScriptParameter.java
@@ -0,0 +1,28 @@
+package org.skriptlang.skript.lang.function;
+
+import ch.njol.skript.lang.Expression;
+
+import java.util.Set;
+
+/**
+ * A parameter for a {@link ch.njol.skript.lang.function.DefaultFunction}.
+ *
+ * @param name The name.
+ * @param type The type's class.
+ * @param modifiers The modifiers.
+ * @param defaultValue The default value, or null if there is no default value.
+ * @param The type.
+ */
+public record ScriptParameter(String name, Class type, Set modifiers, Expression> defaultValue)
+ implements Parameter {
+
+ public ScriptParameter(String name, Class type, Modifier... modifiers) {
+ this(name, type, Set.of(modifiers), null);
+ }
+
+ public ScriptParameter(String name, Class type, Expression> defaultValue, Modifier... modifiers) {
+ this(name, type, Set.of(modifiers), defaultValue);
+ }
+
+}
+
diff --git a/src/test/java/ch/njol/skript/lang/function/DefaultFunctionTest.java b/src/test/java/ch/njol/skript/lang/function/DefaultFunctionTest.java
new file mode 100644
index 00000000000..3d169d61178
--- /dev/null
+++ b/src/test/java/ch/njol/skript/lang/function/DefaultFunctionTest.java
@@ -0,0 +1,83 @@
+package ch.njol.skript.lang.function;
+
+import ch.njol.skript.Skript;
+import ch.njol.util.StringUtils;
+import org.junit.Test;
+import org.skriptlang.skript.addon.SkriptAddon;
+import org.skriptlang.skript.lang.function.Parameter.Modifier;
+
+import static org.junit.Assert.*;
+
+public class DefaultFunctionTest {
+
+ private static final SkriptAddon SKRIPT = Skript.getAddonInstance();
+
+ @Test
+ public void testStrings() {
+ DefaultFunction built = DefaultFunction.builder(SKRIPT, "test", String.class)
+ .description()
+ .since()
+ .keywords()
+ .parameter("x", String[].class, Modifier.OPTIONAL)
+ .build(args -> {
+ String[] xes = args.getOrDefault("x", new String[]{""});
+
+ return StringUtils.join(xes, ",");
+ });
+
+ assertEquals("test", built.getName());
+ assertEquals(String.class, built.getReturnType().getC());
+ assertTrue(built.isSingle());
+ assertArrayEquals(new String[]{}, built.description());
+ assertArrayEquals(new String[]{}, built.since());
+ assertArrayEquals(new String[]{}, built.keywords());
+
+ Parameter>[] parameters = built.getParameters();
+
+ assertEquals(new Parameter<>("x", DefaultFunction.getClassInfo(String[].class), false, null, false, true), parameters[0]);
+ }
+
+ @Test
+ public void testObjectArrays() {
+ DefaultFunction