/*
* This file is part of Skript.
*
* Skript is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Skript is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2013 Peter Güttinger
*
*/
package ch.njol.skript.lang.function;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;
import ch.njol.skript.Skript;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.config.Node;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.log.RetainingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.util.StringUtils;
import ch.njol.util.coll.CollectionUtils;
/**
* @author Peter Güttinger
*/
public class FunctionReference<T> {
final String functionName;
private Function<? extends T> function;
private boolean singleUberParam;
private final Expression<?>[] parameters;
private boolean single;
@Nullable
private final Class<? extends T>[] returnTypes;
@Nullable
private final Node node;
@Nullable
public final File script;
@SuppressWarnings("null")
public FunctionReference(final String functionName, final @Nullable Node node, @Nullable final File script, @Nullable final Class<? extends T>[] returnTypes, final Expression<?>[] params) {
this.functionName = functionName;
this.node = node;
this.script = script;
this.returnTypes = returnTypes;
parameters = params;
}
@SuppressWarnings("unchecked")
public boolean validateFunction(final boolean first) {
final Function<?> newFunc = Functions.getFunction(functionName);
SkriptLogger.setNode(node);
if (newFunc == null) {
if (first)
Skript.error("The function '" + functionName + "' does not exist.");
else
Skript.error("The function '" + functionName + "' was deleted or renamed, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
return false;
}
if (newFunc == function)
return true;
final Class<? extends T>[] returnTypes = this.returnTypes;
if (returnTypes != null) {
final ClassInfo<?> rt = newFunc.returnType;
if (rt == null) {
if (first)
Skript.error("The function '" + functionName + "' doesn't return any value.");
else
Skript.error("The function '" + functionName + "' was redefined with no return value, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
return false;
}
if (!CollectionUtils.containsAnySuperclass(returnTypes, rt.getC())) {
if (first)
Skript.error("The returned value of the function '" + functionName + "', " + newFunc.returnType + ", is " + SkriptParser.notOfType(returnTypes) + ".");
else
Skript.error("The function '" + functionName + "' was redefined with a different, incompatible return type, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
return false;
}
if (first) {
single = newFunc.single;
} else if (single && !newFunc.single) {
Skript.error("The function '" + functionName + "' was redefined with a different, incompatible return type, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
return false;
}
}
// check number of parameters only if the function does not have a single parameter that accepts multiple values
singleUberParam = newFunc.getMaxParameters() == 1 && !newFunc.parameters[0].single;
if (!singleUberParam) {
if (parameters.length > newFunc.getMaxParameters()) {
if (first) {
if (newFunc.getMaxParameters() == 0)
Skript.error("The function '" + functionName + "' 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 '" + functionName + "' has only " + newFunc.getMaxParameters() + " argument" + (newFunc.getMaxParameters() == 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))'");
} else {
Skript.error("The function '" + functionName + "' was redefined with a different, incompatible amount of arguments, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
}
return false;
}
}
if (parameters.length < newFunc.getMinParameters()) {
if (first)
Skript.error("The function '" + functionName + "' requires at least " + newFunc.getMinParameters() + " argument" + (newFunc.getMinParameters() == 1 ? "" : "s") + ","
+ " but only " + parameters.length + " " + (parameters.length == 1 ? "is" : "are") + " given.");
else
Skript.error("The function '" + functionName + "' was redefined with a different, incompatible amount of arguments, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
return false;
}
for (int i = 0; i < parameters.length; i++) {
final Parameter<?> p = newFunc.parameters[singleUberParam ? 0 : i];
final RetainingLogHandler log = SkriptLogger.startRetainingLog();
try {
final Expression<?> e = parameters[i].getConvertedExpression(p.type.getC());
if (e == null) {
if (first)
Skript.error("The " + StringUtils.fancyOrderNumber(i + 1) + " argument given to the function '" + functionName + "' is not of the required type " + p.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 will suppress this error, but it still won't work.");
else
Skript.error("The function '" + functionName + "' was redefined with different, incompatible arguments, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
return false;
}
parameters[i] = e;
} finally {
log.printLog();
}
}
function = (Function<? extends T>) newFunc;
Functions.registerCaller(this);
return true;
}
@Nullable
protected T[] execute(final Event e) {
final Object[][] params = new Object[singleUberParam ? 1 : parameters.length][];
if (singleUberParam && parameters.length > 1) {
final ArrayList<Object> l = new ArrayList<Object>();
for (int i = 0; i < params.length; i++)
l.addAll(Arrays.asList(parameters[i].getArray(e))); // TODO what if an argument is not available? pass null or abort?
params[0] = l.toArray();
} else {
for (int i = 0; i < params.length; i++)
params[i] = parameters[i].getArray(e); // TODO what if an argument is not available? pass null or abort?
}
return function.execute(params);
}
public boolean isSingle() {
return single;
}
@SuppressWarnings("null")
public Class<? extends T> getReturnType() {
return function.returnType.getC();
}
public String toString(@Nullable final Event e, final boolean debug) {
final StringBuilder b = new StringBuilder(functionName + "(");
for (int i = 0; i < parameters.length; i++) {
if (i != 0)
b.append(", ");
b.append(parameters[i].toString(e, debug));
}
return "" + b.append(")");
}
}