1 /*
   2  * Copyright (c) 2005, 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 javax.script;
  27 import java.util.*;
  28 import java.security.*;
  29 import java.util.ServiceLoader;
  30 import java.util.ServiceConfigurationError;
  31 
  32 /**
  33  * The <code>ScriptEngineManager</code> implements a discovery and instantiation
  34  * mechanism for <code>ScriptEngine</code> classes and also maintains a
  35  * collection of key/value pairs storing state shared by all engines created
  36  * by the Manager. This class uses the service provider mechanism described in the
  37  * {@link java.util.ServiceLoader} class to enumerate all the
  38  * implementations of <code>ScriptEngineFactory</code>. <br><br>
  39  * The <code>ScriptEngineManager</code> provides a method to return a list of all these factories
  40  * as well as utility methods which look up factories on the basis of language name, file extension
  41  * and mime type.
  42  * <p>
  43  * The <code>Bindings</code> of key/value pairs, referred to as the "Global Scope"  maintained
  44  * by the manager is available to all instances of <code>ScriptEngine</code> created
  45  * by the <code>ScriptEngineManager</code>.  The values in the <code>Bindings</code> are
  46  * generally exposed in all scripts.
  47  *
  48  * @author Mike Grogan
  49  * @author A. Sundararajan
  50  * @since 1.6
  51  */
  52 public class ScriptEngineManager  {
  53     private static final boolean DEBUG = false;
  54     /**
  55      * The effect of calling this constructor is the same as calling
  56      * <code>ScriptEngineManager(Thread.currentThread().getContextClassLoader())</code>.
  57      *
  58      * @see java.lang.Thread#getContextClassLoader
  59      */
  60     public ScriptEngineManager() {
  61         ClassLoader ctxtLoader = Thread.currentThread().getContextClassLoader();
  62         init(ctxtLoader);
  63     }
  64 
  65     /**
  66      * This constructor loads the implementations of
  67      * <code>ScriptEngineFactory</code> visible to the given
  68      * <code>ClassLoader</code> using the service provider mechanism.<br><br>
  69      * If loader is <code>null</code>, the script engine factories that are
  70      * bundled with the platform are loaded. <br>
  71      *
  72      * @param loader ClassLoader used to discover script engine factories.
  73      */
  74     public ScriptEngineManager(ClassLoader loader) {
  75         init(loader);
  76     }
  77 
  78     private void init(final ClassLoader loader) {
  79         globalScope = new SimpleBindings();
  80         engineSpis = new TreeSet<ScriptEngineFactory>(Comparator.comparing(
  81             ScriptEngineFactory::getEngineName,
  82             Comparator.nullsLast(Comparator.naturalOrder()))
  83         );
  84         nameAssociations = new HashMap<String, ScriptEngineFactory>();
  85         extensionAssociations = new HashMap<String, ScriptEngineFactory>();
  86         mimeTypeAssociations = new HashMap<String, ScriptEngineFactory>();
  87         initEngines(loader);
  88     }
  89 
  90     private ServiceLoader<ScriptEngineFactory> getServiceLoader(final ClassLoader loader) {
  91         if (loader != null) {
  92             return ServiceLoader.load(ScriptEngineFactory.class, loader);
  93         } else {
  94             return ServiceLoader.loadInstalled(ScriptEngineFactory.class);
  95         }
  96     }
  97 
  98     private void initEngines(final ClassLoader loader) {
  99         Iterator<ScriptEngineFactory> itr = null;
 100         try {
 101             ServiceLoader<ScriptEngineFactory> sl = AccessController.doPrivileged(
 102                 new PrivilegedAction<ServiceLoader<ScriptEngineFactory>>() {
 103                     @Override
 104                     public ServiceLoader<ScriptEngineFactory> run() {
 105                         return getServiceLoader(loader);
 106                     }
 107                 });
 108 
 109             itr = sl.iterator();
 110         } catch (ServiceConfigurationError err) {
 111             System.err.println("Can't find ScriptEngineFactory providers: " +
 112                           err.getMessage());
 113             if (DEBUG) {
 114                 err.printStackTrace();
 115             }
 116             // do not throw any exception here. user may want to
 117             // manage his/her own factories using this manager
 118             // by explicit registratation (by registerXXX) methods.
 119             return;
 120         }
 121 
 122         try {
 123             while (itr.hasNext()) {
 124                 try {
 125                     ScriptEngineFactory fact = itr.next();
 126                     engineSpis.add(fact);
 127                 } catch (ServiceConfigurationError err) {
 128                     System.err.println("ScriptEngineManager providers.next(): "
 129                                  + err.getMessage());
 130                     if (DEBUG) {
 131                         err.printStackTrace();
 132                     }
 133                     // one factory failed, but check other factories...
 134                     continue;
 135                 }
 136             }
 137         } catch (ServiceConfigurationError err) {
 138             System.err.println("ScriptEngineManager providers.hasNext(): "
 139                             + err.getMessage());
 140             if (DEBUG) {
 141                 err.printStackTrace();
 142             }
 143             // do not throw any exception here. user may want to
 144             // manage his/her own factories using this manager
 145             // by explicit registratation (by registerXXX) methods.
 146             return;
 147         }
 148     }
 149 
 150     /**
 151      * <code>setBindings</code> stores the specified <code>Bindings</code>
 152      * in the <code>globalScope</code> field. ScriptEngineManager sets this
 153      * <code>Bindings</code> as global bindings for <code>ScriptEngine</code>
 154      * objects created by it.
 155      *
 156      * @param bindings The specified <code>Bindings</code>
 157      * @throws IllegalArgumentException if bindings is null.
 158      */
 159     public void setBindings(Bindings bindings) {
 160         if (bindings == null) {
 161             throw new IllegalArgumentException("Global scope cannot be null.");
 162         }
 163 
 164         globalScope = bindings;
 165     }
 166 
 167     /**
 168      * <code>getBindings</code> returns the value of the <code>globalScope</code> field.
 169      * ScriptEngineManager sets this <code>Bindings</code> as global bindings for
 170      * <code>ScriptEngine</code> objects created by it.
 171      *
 172      * @return The globalScope field.
 173      */
 174     public Bindings getBindings() {
 175         return globalScope;
 176     }
 177 
 178     /**
 179      * Sets the specified key/value pair in the Global Scope.
 180      * @param key Key to set
 181      * @param value Value to set.
 182      * @throws NullPointerException if key is null.
 183      * @throws IllegalArgumentException if key is empty string.
 184      */
 185     public void put(String key, Object value) {
 186         globalScope.put(key, value);
 187     }
 188 
 189     /**
 190      * Gets the value for the specified key in the Global Scope
 191      * @param key The key whose value is to be returned.
 192      * @return The value for the specified key.
 193      */
 194     public Object get(String key) {
 195         return globalScope.get(key);
 196     }
 197 
 198     /**
 199      * Looks up and creates a <code>ScriptEngine</code> for a given  name.
 200      * The algorithm first searches for a <code>ScriptEngineFactory</code> that has been
 201      * registered as a handler for the specified name using the <code>registerEngineName</code>
 202      * method.
 203      * <br><br> If one is not found, it searches the set of <code>ScriptEngineFactory</code> instances
 204      * stored by the constructor for one with the specified name.  If a <code>ScriptEngineFactory</code>
 205      * is found by either method, it is used to create instance of <code>ScriptEngine</code>.
 206      * @param shortName The short name of the <code>ScriptEngine</code> implementation.
 207      * returned by the <code>getNames</code> method of its <code>ScriptEngineFactory</code>.
 208      * @return A <code>ScriptEngine</code> created by the factory located in the search.  Returns null
 209      * if no such factory was found.  The <code>ScriptEngineManager</code> sets its own <code>globalScope</code>
 210      * <code>Bindings</code> as the <code>GLOBAL_SCOPE</code> <code>Bindings</code> of the newly
 211      * created <code>ScriptEngine</code>.
 212      * @throws NullPointerException if shortName is null.
 213      */
 214     public ScriptEngine getEngineByName(String shortName) {
 215         if (shortName == null) throw new NullPointerException();
 216         //look for registered name first
 217         Object obj;
 218         if (null != (obj = nameAssociations.get(shortName))) {
 219             ScriptEngineFactory spi = (ScriptEngineFactory)obj;
 220             try {
 221                 ScriptEngine engine = spi.getScriptEngine();
 222                 engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
 223                 return engine;
 224             } catch (Exception exp) {
 225                 if (DEBUG) exp.printStackTrace();
 226             }
 227         }
 228 
 229         for (ScriptEngineFactory spi : engineSpis) {
 230             List<String> names = null;
 231             try {
 232                 names = spi.getNames();
 233             } catch (Exception exp) {
 234                 if (DEBUG) exp.printStackTrace();
 235             }
 236 
 237             if (names != null) {
 238                 for (String name : names) {
 239                     if (shortName.equals(name)) {
 240                         try {
 241                             ScriptEngine engine = spi.getScriptEngine();
 242                             engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
 243                             return engine;
 244                         } catch (Exception exp) {
 245                             if (DEBUG) exp.printStackTrace();
 246                         }
 247                     }
 248                 }
 249             }
 250         }
 251 
 252         return null;
 253     }
 254 
 255     /**
 256      * Look up and create a <code>ScriptEngine</code> for a given extension.  The algorithm
 257      * used by <code>getEngineByName</code> is used except that the search starts
 258      * by looking for a <code>ScriptEngineFactory</code> registered to handle the
 259      * given extension using <code>registerEngineExtension</code>.
 260      * @param extension The given extension
 261      * @return The engine to handle scripts with this extension.  Returns <code>null</code>
 262      * if not found.
 263      * @throws NullPointerException if extension is null.
 264      */
 265     public ScriptEngine getEngineByExtension(String extension) {
 266         if (extension == null) throw new NullPointerException();
 267         //look for registered extension first
 268         Object obj;
 269         if (null != (obj = extensionAssociations.get(extension))) {
 270             ScriptEngineFactory spi = (ScriptEngineFactory)obj;
 271             try {
 272                 ScriptEngine engine = spi.getScriptEngine();
 273                 engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
 274                 return engine;
 275             } catch (Exception exp) {
 276                 if (DEBUG) exp.printStackTrace();
 277             }
 278         }
 279 
 280         for (ScriptEngineFactory spi : engineSpis) {
 281             List<String> exts = null;
 282             try {
 283                 exts = spi.getExtensions();
 284             } catch (Exception exp) {
 285                 if (DEBUG) exp.printStackTrace();
 286             }
 287             if (exts == null) continue;
 288             for (String ext : exts) {
 289                 if (extension.equals(ext)) {
 290                     try {
 291                         ScriptEngine engine = spi.getScriptEngine();
 292                         engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
 293                         return engine;
 294                     } catch (Exception exp) {
 295                         if (DEBUG) exp.printStackTrace();
 296                     }
 297                 }
 298             }
 299         }
 300         return null;
 301     }
 302 
 303     /**
 304      * Look up and create a <code>ScriptEngine</code> for a given mime type.  The algorithm
 305      * used by <code>getEngineByName</code> is used except that the search starts
 306      * by looking for a <code>ScriptEngineFactory</code> registered to handle the
 307      * given mime type using <code>registerEngineMimeType</code>.
 308      * @param mimeType The given mime type
 309      * @return The engine to handle scripts with this mime type.  Returns <code>null</code>
 310      * if not found.
 311      * @throws NullPointerException if mimeType is null.
 312      */
 313     public ScriptEngine getEngineByMimeType(String mimeType) {
 314         if (mimeType == null) throw new NullPointerException();
 315         //look for registered types first
 316         Object obj;
 317         if (null != (obj = mimeTypeAssociations.get(mimeType))) {
 318             ScriptEngineFactory spi = (ScriptEngineFactory)obj;
 319             try {
 320                 ScriptEngine engine = spi.getScriptEngine();
 321                 engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
 322                 return engine;
 323             } catch (Exception exp) {
 324                 if (DEBUG) exp.printStackTrace();
 325             }
 326         }
 327 
 328         for (ScriptEngineFactory spi : engineSpis) {
 329             List<String> types = null;
 330             try {
 331                 types = spi.getMimeTypes();
 332             } catch (Exception exp) {
 333                 if (DEBUG) exp.printStackTrace();
 334             }
 335             if (types == null) continue;
 336             for (String type : types) {
 337                 if (mimeType.equals(type)) {
 338                     try {
 339                         ScriptEngine engine = spi.getScriptEngine();
 340                         engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
 341                         return engine;
 342                     } catch (Exception exp) {
 343                         if (DEBUG) exp.printStackTrace();
 344                     }
 345                 }
 346             }
 347         }
 348         return null;
 349     }
 350 
 351     /**
 352      * Returns a list whose elements are instances of all the <code>ScriptEngineFactory</code> classes
 353      * found by the discovery mechanism.
 354      * @return List of all discovered <code>ScriptEngineFactory</code>s.
 355      */
 356     public List<ScriptEngineFactory> getEngineFactories() {
 357         List<ScriptEngineFactory> res = new ArrayList<ScriptEngineFactory>(engineSpis.size());
 358         for (ScriptEngineFactory spi : engineSpis) {
 359             res.add(spi);
 360         }
 361         return Collections.unmodifiableList(res);
 362     }
 363 
 364     /**
 365      * Registers a <code>ScriptEngineFactory</code> to handle a language
 366      * name.  Overrides any such association found using the Discovery mechanism.
 367      * @param name The name to be associated with the <code>ScriptEngineFactory</code>.
 368      * @param factory The class to associate with the given name.
 369      * @throws NullPointerException if any of the parameters is null.
 370      */
 371     public void registerEngineName(String name, ScriptEngineFactory factory) {
 372         if (name == null || factory == null) throw new NullPointerException();
 373         nameAssociations.put(name, factory);
 374     }
 375 
 376     /**
 377      * Registers a <code>ScriptEngineFactory</code> to handle a mime type.
 378      * Overrides any such association found using the Discovery mechanism.
 379      *
 380      * @param type The mime type  to be associated with the
 381      * <code>ScriptEngineFactory</code>.
 382      *
 383      * @param factory The class to associate with the given mime type.
 384      * @throws NullPointerException if any of the parameters is null.
 385      */
 386     public void registerEngineMimeType(String type, ScriptEngineFactory factory) {
 387         if (type == null || factory == null) throw new NullPointerException();
 388         mimeTypeAssociations.put(type, factory);
 389     }
 390 
 391     /**
 392      * Registers a <code>ScriptEngineFactory</code> to handle an extension.
 393      * Overrides any such association found using the Discovery mechanism.
 394      *
 395      * @param extension The extension type  to be associated with the
 396      * <code>ScriptEngineFactory</code>.
 397      * @param factory The class to associate with the given extension.
 398      * @throws NullPointerException if any of the parameters is null.
 399      */
 400     public void registerEngineExtension(String extension, ScriptEngineFactory factory) {
 401         if (extension == null || factory == null) throw new NullPointerException();
 402         extensionAssociations.put(extension, factory);
 403     }
 404 
 405     /** Set of script engine factories discovered. */
 406     private TreeSet<ScriptEngineFactory> engineSpis;
 407 
 408     /** Map of engine name to script engine factory. */
 409     private HashMap<String, ScriptEngineFactory> nameAssociations;
 410 
 411     /** Map of script file extension to script engine factory. */
 412     private HashMap<String, ScriptEngineFactory> extensionAssociations;
 413 
 414     /** Map of script MIME type to script engine factory. */
 415     private HashMap<String, ScriptEngineFactory> mimeTypeAssociations;
 416 
 417     /** Global bindings associated with script engines created by this manager. */
 418     private Bindings globalScope;
 419 }