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.api.scripting;
27
28import java.util.Arrays;
29import java.util.Collections;
30import java.util.List;
31import java.util.Objects;
32import javax.script.ScriptEngine;
33import javax.script.ScriptEngineFactory;
34import jdk.nashorn.internal.runtime.Context;
35import jdk.nashorn.internal.runtime.Version;
36
37/**
38 * JSR-223 compliant script engine factory for Nashorn. The engine answers for:
39 * <ul>
40 * <li>names {@code "nashorn"}, {@code "Nashorn"}, {@code "js"}, {@code "JS"}, {@code "JavaScript"},
41 * {@code "javascript"}, {@code "ECMAScript"}, and {@code "ecmascript"};</li>
42 * <li>MIME types {@code "application/javascript"}, {@code "application/ecmascript"}, {@code "text/javascript"}, and
43 * {@code "text/ecmascript"};</li>
44 * <li>as well as for the extension {@code "js"}.</li>
45 * </ul>
46 * Programs executing in engines created using {@link #getScriptEngine(String[])} will have the passed arguments
47 * accessible as a global variable named {@code "arguments"}.
48 *
49 * @since 1.8u40
50 */
51public final class NashornScriptEngineFactory implements ScriptEngineFactory {
52    @Override
53    public String getEngineName() {
54        return (String) getParameter(ScriptEngine.ENGINE);
55    }
56
57    @Override
58    public String getEngineVersion() {
59        return (String) getParameter(ScriptEngine.ENGINE_VERSION);
60    }
61
62    @Override
63    public List<String> getExtensions() {
64        return Collections.unmodifiableList(extensions);
65    }
66
67    @Override
68    public String getLanguageName() {
69        return (String) getParameter(ScriptEngine.LANGUAGE);
70    }
71
72    @Override
73    public String getLanguageVersion() {
74        return (String) getParameter(ScriptEngine.LANGUAGE_VERSION);
75    }
76
77    @Override
78    public String getMethodCallSyntax(final String obj, final String method, final String... args) {
79        final StringBuilder sb = new StringBuilder().append(obj).append('.').append(method).append('(');
80        final int len = args.length;
81
82        if (len > 0) {
83            sb.append(args[0]);
84        }
85        for (int i = 1; i < len; i++) {
86            sb.append(',').append(args[i]);
87        }
88        sb.append(')');
89
90        return sb.toString();
91    }
92
93    @Override
94    public List<String> getMimeTypes() {
95        return Collections.unmodifiableList(mimeTypes);
96    }
97
98    @Override
99    public List<String> getNames() {
100        return Collections.unmodifiableList(names);
101    }
102
103    @Override
104    public String getOutputStatement(final String toDisplay) {
105        return "print(" + toDisplay + ")";
106    }
107
108    @Override
109    public Object getParameter(final String key) {
110        switch (key) {
111        case ScriptEngine.NAME:
112            return "javascript";
113        case ScriptEngine.ENGINE:
114            return "Oracle Nashorn";
115        case ScriptEngine.ENGINE_VERSION:
116            return Version.version();
117        case ScriptEngine.LANGUAGE:
118            return "ECMAScript";
119        case ScriptEngine.LANGUAGE_VERSION:
120            return "ECMA - 262 Edition 5.1";
121        case "THREADING":
122            // The engine implementation is not thread-safe. Can't be
123            // used to execute scripts concurrently on multiple threads.
124            return null;
125        default:
126            return null;
127        }
128    }
129
130    @Override
131    public String getProgram(final String... statements) {
132        Objects.requireNonNull(statements);
133        final StringBuilder sb = new StringBuilder();
134
135        for (final String statement : statements) {
136            sb.append(Objects.requireNonNull(statement)).append(';');
137        }
138
139        return sb.toString();
140    }
141
142    // default options passed to Nashorn script engine
143    private static final String[] DEFAULT_OPTIONS = new String[] { "-doe" };
144
145    @Override
146    public ScriptEngine getScriptEngine() {
147        try {
148            return new NashornScriptEngine(this, DEFAULT_OPTIONS, getAppClassLoader(), null);
149        } catch (final RuntimeException e) {
150            if (Context.DEBUG) {
151                e.printStackTrace();
152            }
153            throw e;
154        }
155    }
156
157    /**
158     * Create a new Script engine initialized with the given class loader.
159     *
160     * @param appLoader class loader to be used as script "app" class loader.
161     * @return newly created script engine.
162     * @throws SecurityException
163     *         if the security manager's {@code checkPermission}
164     *         denies {@code RuntimePermission("nashorn.setConfig")}
165     */
166    public ScriptEngine getScriptEngine(final ClassLoader appLoader) {
167        return newEngine(DEFAULT_OPTIONS, appLoader, null);
168    }
169
170    /**
171     * Create a new Script engine initialized with the given class filter.
172     *
173     * @param classFilter class filter to use.
174     * @return newly created script engine.
175     * @throws NullPointerException if {@code classFilter} is {@code null}
176     * @throws SecurityException
177     *         if the security manager's {@code checkPermission}
178     *         denies {@code RuntimePermission("nashorn.setConfig")}
179     */
180    public ScriptEngine getScriptEngine(final ClassFilter classFilter) {
181        return newEngine(DEFAULT_OPTIONS, getAppClassLoader(), Objects.requireNonNull(classFilter));
182    }
183
184    /**
185     * Create a new Script engine initialized with the given arguments.
186     *
187     * @param args arguments array passed to script engine.
188     * @return newly created script engine.
189     * @throws NullPointerException if {@code args} is {@code null}
190     * @throws SecurityException
191     *         if the security manager's {@code checkPermission}
192     *         denies {@code RuntimePermission("nashorn.setConfig")}
193     */
194    public ScriptEngine getScriptEngine(final String... args) {
195        return newEngine(Objects.requireNonNull(args), getAppClassLoader(), null);
196    }
197
198    /**
199     * Create a new Script engine initialized with the given arguments and the given class loader.
200     *
201     * @param args arguments array passed to script engine.
202     * @param appLoader class loader to be used as script "app" class loader.
203     * @return newly created script engine.
204     * @throws NullPointerException if {@code args} is {@code null}
205     * @throws SecurityException
206     *         if the security manager's {@code checkPermission}
207     *         denies {@code RuntimePermission("nashorn.setConfig")}
208     */
209    public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader) {
210        return newEngine(Objects.requireNonNull(args), appLoader, null);
211    }
212
213    /**
214     * Create a new Script engine initialized with the given arguments, class loader and class filter.
215     *
216     * @param args arguments array passed to script engine.
217     * @param appLoader class loader to be used as script "app" class loader.
218     * @param classFilter class filter to use.
219     * @return newly created script engine.
220     * @throws NullPointerException if {@code args} or {@code classFilter} is {@code null}
221     * @throws SecurityException
222     *         if the security manager's {@code checkPermission}
223     *         denies {@code RuntimePermission("nashorn.setConfig")}
224     */
225    public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
226        return newEngine(Objects.requireNonNull(args), appLoader, Objects.requireNonNull(classFilter));
227    }
228
229    private ScriptEngine newEngine(final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
230        checkConfigPermission();
231        try {
232            return new NashornScriptEngine(this, args, appLoader, classFilter);
233        } catch (final RuntimeException e) {
234            if (Context.DEBUG) {
235                e.printStackTrace();
236            }
237            throw e;
238        }
239    }
240
241    // -- Internals only below this point
242
243    private static void checkConfigPermission() {
244        final SecurityManager sm = System.getSecurityManager();
245        if (sm != null) {
246            sm.checkPermission(new RuntimePermission(Context.NASHORN_SET_CONFIG));
247        }
248    }
249
250    private static final List<String> names;
251    private static final List<String> mimeTypes;
252    private static final List<String> extensions;
253
254    static {
255        names = immutableList(
256                    "nashorn", "Nashorn",
257                    "js", "JS",
258                    "JavaScript", "javascript",
259                    "ECMAScript", "ecmascript"
260                );
261
262        mimeTypes = immutableList(
263                        "application/javascript",
264                        "application/ecmascript",
265                        "text/javascript",
266                        "text/ecmascript"
267                    );
268
269        extensions = immutableList("js");
270    }
271
272    private static List<String> immutableList(final String... elements) {
273        return Collections.unmodifiableList(Arrays.asList(elements));
274    }
275
276    private static ClassLoader getAppClassLoader() {
277        // Revisit: script engine implementation needs the capability to
278        // find the class loader of the context in which the script engine
279        // is running so that classes will be found and loaded properly
280        final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
281        return (ccl == null)? NashornScriptEngineFactory.class.getClassLoader() : ccl;
282    }
283}
284