NativeFunction.java revision 1354:a5e202d6eb99
1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.objects;
27
28import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
29import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
31import static jdk.nashorn.internal.runtime.Source.sourceFor;
32
33import java.lang.invoke.MethodHandle;
34import java.lang.invoke.MethodHandles;
35import java.util.List;
36import jdk.internal.dynalink.support.Lookup;
37import jdk.nashorn.api.scripting.JSObject;
38import jdk.nashorn.internal.objects.annotations.Attribute;
39import jdk.nashorn.internal.objects.annotations.Constructor;
40import jdk.nashorn.internal.objects.annotations.Function;
41import jdk.nashorn.internal.objects.annotations.ScriptClass;
42import jdk.nashorn.internal.parser.Parser;
43import jdk.nashorn.internal.runtime.Context;
44import jdk.nashorn.internal.runtime.JSType;
45import jdk.nashorn.internal.runtime.ParserException;
46import jdk.nashorn.internal.runtime.PropertyMap;
47import jdk.nashorn.internal.runtime.ScriptEnvironment;
48import jdk.nashorn.internal.runtime.ScriptFunction;
49import jdk.nashorn.internal.runtime.ScriptObject;
50import jdk.nashorn.internal.runtime.ScriptRuntime;
51import jdk.nashorn.internal.runtime.linker.Bootstrap;
52
53/**
54 * ECMA 15.3 Function Objects
55 *
56 * Note: instances of this class are never created. This class is not even a
57 * subclass of ScriptObject. But, we use this class to generate prototype and
58 * constructor for "Function".
59 */
60@ScriptClass("Function")
61public final class NativeFunction {
62
63    /** apply arg converter handle */
64    public static final MethodHandle TO_APPLY_ARGS = Lookup.findOwnStatic(MethodHandles.lookup(), "toApplyArgs", Object[].class, Object.class);
65
66    // initialized by nasgen
67    @SuppressWarnings("unused")
68    private static PropertyMap $nasgenmap$;
69
70    // do *not* create me!
71    private NativeFunction() {
72        throw new UnsupportedOperationException();
73    }
74
75    /**
76     * ECMA 15.3.4.2 Function.prototype.toString ( )
77     *
78     * @param self self reference
79     * @return string representation of Function
80     */
81    @Function(attributes = Attribute.NOT_ENUMERABLE)
82    public static String toString(final Object self) {
83        if (!(self instanceof ScriptFunction)) {
84            throw typeError("not.a.function", ScriptRuntime.safeToString(self));
85        }
86        return ((ScriptFunction)self).toSource();
87    }
88
89    /**
90     * ECMA 15.3.4.3 Function.prototype.apply (thisArg, argArray)
91     *
92     * @param self   self reference
93     * @param thiz   {@code this} arg for apply
94     * @param array  array of argument for apply
95     * @return result of apply
96     */
97    @Function(attributes = Attribute.NOT_ENUMERABLE)
98    public static Object apply(final Object self, final Object thiz, final Object array) {
99        checkCallable(self);
100
101        final Object[] args = toApplyArgs(array);
102
103        if (self instanceof ScriptFunction) {
104            return ScriptRuntime.apply((ScriptFunction)self, thiz, args);
105        } else if (self instanceof JSObject) {
106            return ((JSObject)self).call(thiz, args);
107        }
108        throw new AssertionError("Should not reach here");
109    }
110
111    /**
112     * Given an array-like object, converts it into a Java object array suitable for invocation of ScriptRuntime.apply
113     * or for direct invocation of the applied function.
114     * @param array the array-like object. Can be null in which case a zero-length array is created.
115     * @return the Java array
116     */
117    public static Object[] toApplyArgs(final Object array) {
118        if (array instanceof NativeArguments) {
119            return ((NativeArguments)array).getArray().asObjectArray();
120        } else if (array instanceof ScriptObject) {
121            // look for array-like object
122            final ScriptObject sobj = (ScriptObject)array;
123            final int n = lengthToInt(sobj.getLength());
124
125            final Object[] args = new Object[n];
126            for (int i = 0; i < args.length; i++) {
127                args[i] = sobj.get(i);
128            }
129            return args;
130        } else if (array instanceof Object[]) {
131            return (Object[])array;
132        } else if (array instanceof List) {
133            final List<?> list = (List<?>)array;
134            return list.toArray(new Object[list.size()]);
135        } else if (array == null || array == UNDEFINED) {
136            return ScriptRuntime.EMPTY_ARRAY;
137        } else if (array instanceof JSObject) {
138            // look for array-like JSObject object
139            final JSObject jsObj = (JSObject)array;
140            final Object   len  = jsObj.hasMember("length")? jsObj.getMember("length") : Integer.valueOf(0);
141            final int n = lengthToInt(len);
142
143            final Object[] args = new Object[n];
144            for (int i = 0; i < args.length; i++) {
145                args[i] = jsObj.hasSlot(i)? jsObj.getSlot(i) : UNDEFINED;
146            }
147            return args;
148        } else {
149            throw typeError("function.apply.expects.array");
150        }
151    }
152
153    private static int lengthToInt(final Object len) {
154        final long ln = JSType.toUint32(len);
155        // NOTE: ECMASCript 5.1 section 15.3.4.3 says length should be treated as Uint32, but we wouldn't be able to
156        // allocate a Java array of more than MAX_VALUE elements anyway, so at this point we have to throw an error.
157        // People applying a function to more than 2^31 arguments will unfortunately be out of luck.
158        if (ln > Integer.MAX_VALUE) {
159            throw rangeError("range.error.inappropriate.array.length", JSType.toString(len));
160        }
161        return (int)ln;
162    }
163
164    private static void checkCallable(final Object self) {
165        if (!(self instanceof ScriptFunction || (self instanceof JSObject && ((JSObject)self).isFunction()))) {
166            throw typeError("not.a.function", ScriptRuntime.safeToString(self));
167        }
168    }
169
170    /**
171     * ECMA 15.3.4.4 Function.prototype.call (thisArg [ , arg1 [ , arg2, ... ] ] )
172     *
173     * @param self self reference
174     * @param args arguments for call
175     * @return result of call
176     */
177    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
178    public static Object call(final Object self, final Object... args) {
179        checkCallable(self);
180
181        final Object thiz = (args.length == 0) ? UNDEFINED : args[0];
182        Object[] arguments;
183
184        if (args.length > 1) {
185            arguments = new Object[args.length - 1];
186            System.arraycopy(args, 1, arguments, 0, arguments.length);
187        } else {
188            arguments = ScriptRuntime.EMPTY_ARRAY;
189        }
190
191        if (self instanceof ScriptFunction) {
192            return ScriptRuntime.apply((ScriptFunction)self, thiz, arguments);
193        } else if (self instanceof JSObject) {
194            return ((JSObject)self).call(thiz, arguments);
195        }
196
197        throw new AssertionError("should not reach here");
198    }
199
200    /**
201     * ECMA 15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, ...]])
202     *
203     * @param self self reference
204     * @param args arguments for bind
205     * @return function with bound arguments
206     */
207    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
208    public static Object bind(final Object self, final Object... args) {
209        final Object thiz = (args.length == 0) ? UNDEFINED : args[0];
210
211        Object[] arguments;
212        if (args.length > 1) {
213            arguments = new Object[args.length - 1];
214            System.arraycopy(args, 1, arguments, 0, arguments.length);
215        } else {
216            arguments = ScriptRuntime.EMPTY_ARRAY;
217        }
218
219        return Bootstrap.bindCallable(self, thiz, arguments);
220    }
221
222    /**
223     * Nashorn extension: Function.prototype.toSource
224     *
225     * @param self self reference
226     * @return source for function
227     */
228    @Function(attributes = Attribute.NOT_ENUMERABLE)
229    public static String toSource(final Object self) {
230        if (!(self instanceof ScriptFunction)) {
231            throw typeError("not.a.function", ScriptRuntime.safeToString(self));
232        }
233        return ((ScriptFunction)self).toSource();
234    }
235
236    /**
237     * ECMA 15.3.2.1 new Function (p1, p2, ... , pn, body)
238     *
239     * Constructor
240     *
241     * @param newObj is the new operator used for constructing this function
242     * @param self   self reference
243     * @param args   arguments
244     * @return new NativeFunction
245     */
246    @Constructor(arity = 1)
247    public static ScriptFunction function(final boolean newObj, final Object self, final Object... args) {
248        final StringBuilder sb = new StringBuilder();
249
250        sb.append("(function (");
251        final String funcBody;
252        if (args.length > 0) {
253            final StringBuilder paramListBuf = new StringBuilder();
254            for (int i = 0; i < args.length - 1; i++) {
255                paramListBuf.append(JSType.toString(args[i]));
256                if (i < args.length - 2) {
257                    paramListBuf.append(",");
258                }
259            }
260
261            // now convert function body to a string
262            funcBody = JSType.toString(args[args.length - 1]);
263
264            final String paramList = paramListBuf.toString();
265            if (!paramList.isEmpty()) {
266                checkFunctionParameters(paramList);
267                sb.append(paramList);
268            }
269        } else {
270            funcBody = null;
271        }
272
273        sb.append(") {\n");
274        if (args.length > 0) {
275            checkFunctionBody(funcBody);
276            sb.append(funcBody);
277            sb.append('\n');
278        }
279        sb.append("})");
280
281        final Global global = Global.instance();
282        final Context context = global.getContext();
283        return (ScriptFunction)context.eval(global, sb.toString(), global, "<function>");
284    }
285
286    private static void checkFunctionParameters(final String params) {
287        final Parser parser = getParser(params);
288        try {
289            parser.parseFormalParameterList();
290        } catch (final ParserException pe) {
291            pe.throwAsEcmaException();
292        }
293    }
294
295    private static void checkFunctionBody(final String funcBody) {
296        final Parser parser = getParser(funcBody);
297        try {
298            parser.parseFunctionBody();
299        } catch (final ParserException pe) {
300            pe.throwAsEcmaException();
301        }
302    }
303
304    private static Parser getParser(final String sourceText) {
305        final ScriptEnvironment env = Global.getEnv();
306        return new Parser(env, sourceFor("<function>", sourceText), new Context.ThrowErrorManager(), env._strict, null);
307    }
308}
309