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