package jdk.nashorn.internal.objects;
import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import static jdk.nashorn.internal.runtime.Source.sourceFor;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.List;
import jdk.dynalink.linker.support.Lookup;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.objects.annotations.Attribute;
import jdk.nashorn.internal.objects.annotations.Constructor;
import jdk.nashorn.internal.objects.annotations.Function;
import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.parser.Parser;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ParserException;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
@ScriptClass("Function")
public final class NativeFunction {
public static final MethodHandle TO_APPLY_ARGS = Lookup.findOwnStatic(MethodHandles.lookup(), "toApplyArgs", Object[].class, Object.class);
@SuppressWarnings("unused")
private static PropertyMap $nasgenmap$;
private NativeFunction() {
throw new UnsupportedOperationException();
}
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toString(final Object self) {
if (!(self instanceof ScriptFunction)) {
throw typeError("not.a.function", ScriptRuntime.safeToString(self));
}
return ((ScriptFunction)self).toSource();
}
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object apply(final Object self, final Object thiz, final Object array) {
checkCallable(self);
final Object[] args = toApplyArgs(array);
if (self instanceof ScriptFunction) {
return ScriptRuntime.apply((ScriptFunction)self, thiz, args);
} else if (self instanceof ScriptObjectMirror) {
return ((JSObject)self).call(thiz, args);
} else if (self instanceof JSObject) {
final Global global = Global.instance();
final Object result = ((JSObject) self).call(ScriptObjectMirror.wrap(thiz, global),
ScriptObjectMirror.wrapArray(args, global));
return ScriptObjectMirror.unwrap(result, global);
}
throw new AssertionError("Should not reach here");
}
public static Object[] toApplyArgs(final Object array) {
if (array instanceof NativeArguments) {
return ((NativeArguments)array).getArray().asObjectArray();
} else if (array instanceof ScriptObject) {
final ScriptObject sobj = (ScriptObject)array;
final int n = lengthToInt(sobj.getLength());
final Object[] args = new Object[n];
for (int i = 0; i < args.length; i++) {
args[i] = sobj.get(i);
}
return args;
} else if (array instanceof Object[]) {
return (Object[])array;
} else if (array instanceof List) {
final List<?> list = (List<?>)array;
return list.toArray(new Object[0]);
} else if (array == null || array == UNDEFINED) {
return ScriptRuntime.EMPTY_ARRAY;
} else if (array instanceof JSObject) {
final JSObject jsObj = (JSObject)array;
final Object len = jsObj.hasMember("length")? jsObj.getMember("length") : Integer.valueOf(0);
final int n = lengthToInt(len);
final Object[] args = new Object[n];
for (int i = 0; i < args.length; i++) {
args[i] = jsObj.hasSlot(i)? jsObj.getSlot(i) : UNDEFINED;
}
return args;
} else {
throw typeError("function.apply.expects.array");
}
}
private static int lengthToInt(final Object len) {
final long ln = JSType.toUint32(len);
if (ln > Integer.MAX_VALUE) {
throw rangeError("range.error.inappropriate.array.length", JSType.toString(len));
}
return (int)ln;
}
private static void checkCallable(final Object self) {
if (!(self instanceof ScriptFunction || (self instanceof JSObject && ((JSObject)self).isFunction()))) {
throw typeError("not.a.function", ScriptRuntime.safeToString(self));
}
}
@Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
public static Object call(final Object self, final Object... args) {
checkCallable(self);
final Object thiz = (args.length == 0) ? UNDEFINED : args[0];
Object[] arguments;
if (args.length > 1) {
arguments = new Object[args.length - 1];
System.arraycopy(args, 1, arguments, 0, arguments.length);
} else {
arguments = ScriptRuntime.EMPTY_ARRAY;
}
if (self instanceof ScriptFunction) {
return ScriptRuntime.apply((ScriptFunction)self, thiz, arguments);
} else if (self instanceof JSObject) {
return ((JSObject)self).call(thiz, arguments);
}
throw new AssertionError("should not reach here");
}
@Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
public static Object bind(final Object self, final Object... args) {
final Object thiz = (args.length == 0) ? UNDEFINED : args[0];
Object[] arguments;
if (args.length > 1) {
arguments = new Object[args.length - 1];
System.arraycopy(args, 1, arguments, 0, arguments.length);
} else {
arguments = ScriptRuntime.EMPTY_ARRAY;
}
return Bootstrap.bindCallable(self, thiz, arguments);
}
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toSource(final Object self) {
if (!(self instanceof ScriptFunction)) {
throw typeError("not.a.function", ScriptRuntime.safeToString(self));
}
return ((ScriptFunction)self).toSource();
}
@Constructor(arity = 1)
public static ScriptFunction function(final boolean newObj, final Object self, final Object... args) {
final StringBuilder sb = new StringBuilder();
sb.append("(function (");
final String funcBody;
if (args.length > 0) {
final StringBuilder paramListBuf = new StringBuilder();
for (int i = 0; i < args.length - 1; i++) {
paramListBuf.append(JSType.toString(args[i]));
if (i < args.length - 2) {
paramListBuf.append(",");
}
}
funcBody = JSType.toString(args[args.length - 1]);
final String paramList = paramListBuf.toString();
if (!paramList.isEmpty()) {
checkFunctionParameters(paramList);
sb.append(paramList);
}
} else {
funcBody = null;
}
sb.append(") {\n");
if (args.length > 0) {
checkFunctionBody(funcBody);
sb.append(funcBody);
sb.append('\n');
}
sb.append("})");
final Global global = Global.instance();
final Context context = global.getContext();
return (ScriptFunction)context.eval(global, sb.toString(), global, "<function>");
}
private static void checkFunctionParameters(final String params) {
final Parser parser = getParser(params);
try {
parser.parseFormalParameterList();
} catch (final ParserException pe) {
pe.throwAsEcmaException();
}
}
private static void checkFunctionBody(final String funcBody) {
final Parser parser = getParser(funcBody);
try {
parser.parseFunctionBody();
} catch (final ParserException pe) {
pe.throwAsEcmaException();
}
}
private static Parser getParser(final String sourceText) {
final ScriptEnvironment env = Global.getEnv();
return new Parser(env, sourceFor("<function>", sourceText), new Context.ThrowErrorManager(), env._strict, null);
}
}