/* * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.script; import java.util.*; import java.security.*; import java.util.ServiceLoader; import java.util.ServiceConfigurationError; import sun.reflect.Reflection; import sun.security.util.SecurityConstants; /** * The ScriptEngineManager implements a discovery and instantiation * mechanism for ScriptEngine classes and also maintains a * collection of key/value pairs storing state shared by all engines created * by the Manager. This class uses the service provider mechanism to enumerate all the * implementations of ScriptEngineFactory.

* The ScriptEngineManager provides a method to return an array of all these factories * as well as utility methods which look up factories on the basis of language name, file extension * and mime type. *

* The Bindings of key/value pairs, referred to as the "Global Scope" maintained * by the manager is available to all instances of ScriptEngine created * by the ScriptEngineManager. The values in the Bindings are * generally exposed in all scripts. * * @author Mike Grogan * @author A. Sundararajan * @since 1.6 */ public class ScriptEngineManager { private static final boolean DEBUG = false; /** * If the thread context ClassLoader can be accessed by the caller, * then the effect of calling this constructor is the same as calling * ScriptEngineManager(Thread.currentThread().getContextClassLoader()). * Otherwise, the effect is the same as calling ScriptEngineManager(null). * * @see java.lang.Thread#getContextClassLoader */ public ScriptEngineManager() { ClassLoader ctxtLoader = Thread.currentThread().getContextClassLoader(); if (canCallerAccessLoader(ctxtLoader)) { if (DEBUG) System.out.println("using " + ctxtLoader); init(ctxtLoader); } else { if (DEBUG) System.out.println("using bootstrap loader"); init(null); } } /** * This constructor loads the implementations of * ScriptEngineFactory visible to the given * ClassLoader using the service provider mechanism.

* If loader is null, the script engine factories that are * bundled with the platform and that are in the usual extension * directories (installed extensions) are loaded.

* * @param loader ClassLoader used to discover script engine factories. */ public ScriptEngineManager(ClassLoader loader) { init(loader); } private void init(final ClassLoader loader) { globalScope = new SimpleBindings(); engineSpis = new HashSet(); nameAssociations = new HashMap(); extensionAssociations = new HashMap(); mimeTypeAssociations = new HashMap(); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { initEngines(loader); return null; } }); } private void initEngines(final ClassLoader loader) { Iterator itr = null; try { ServiceLoader sl; if (loader != null) { sl = ServiceLoader.load(ScriptEngineFactory.class, loader); } else { sl = ServiceLoader.loadInstalled(ScriptEngineFactory.class); } itr = sl.iterator(); } catch (ServiceConfigurationError err) { System.err.println("Can't find ScriptEngineFactory providers: " + err.getMessage()); if (DEBUG) { err.printStackTrace(); } // do not throw any exception here. user may want to // manage his/her own factories using this manager // by explicit registratation (by registerXXX) methods. return; } try { while (itr.hasNext()) { try { ScriptEngineFactory fact = itr.next(); engineSpis.add(fact); } catch (ServiceConfigurationError err) { System.err.println("ScriptEngineManager providers.next(): " + err.getMessage()); if (DEBUG) { err.printStackTrace(); } // one factory failed, but check other factories... continue; } } } catch (ServiceConfigurationError err) { System.err.println("ScriptEngineManager providers.hasNext(): " + err.getMessage()); if (DEBUG) { err.printStackTrace(); } // do not throw any exception here. user may want to // manage his/her own factories using this manager // by explicit registratation (by registerXXX) methods. return; } } /** * setBindings stores the specified Bindings * in the globalScope field. ScriptEngineManager sets this * Bindings as global bindings for ScriptEngine * objects created by it. * * @param bindings The specified Bindings * @throws IllegalArgumentException if bindings is null. */ public void setBindings(Bindings bindings) { if (bindings == null) { throw new IllegalArgumentException("Global scope cannot be null."); } globalScope = bindings; } /** * getBindings returns the value of the globalScope field. * ScriptEngineManager sets this Bindings as global bindings for * ScriptEngine objects created by it. * * @return The globalScope field. */ public Bindings getBindings() { return globalScope; } /** * Sets the specified key/value pair in the Global Scope. * @param key Key to set * @param value Value to set. * @throws NullPointerException if key is null. * @throws IllegalArgumentException if key is empty string. */ public void put(String key, Object value) { globalScope.put(key, value); } /** * Gets the value for the specified key in the Global Scope * @param key The key whose value is to be returned. * @return The value for the specified key. */ public Object get(String key) { return globalScope.get(key); } /** * Looks up and creates a ScriptEngine for a given name. * The algorithm first searches for a ScriptEngineFactory that has been * registered as a handler for the specified name using the registerEngineName * method. *

If one is not found, it searches the array of ScriptEngineFactory instances * stored by the constructor for one with the specified name. If a ScriptEngineFactory * is found by either method, it is used to create instance of ScriptEngine. * @param shortName The short name of the ScriptEngine implementation. * returned by the getNames method of its ScriptEngineFactory. * @return A ScriptEngine created by the factory located in the search. Returns null * if no such factory was found. The ScriptEngineManager sets its own globalScope * Bindings as the GLOBAL_SCOPE Bindings of the newly * created ScriptEngine. * @throws NullPointerException if shortName is null. */ public ScriptEngine getEngineByName(String shortName) { if (shortName == null) throw new NullPointerException(); //look for registered name first Object obj; if (null != (obj = nameAssociations.get(shortName))) { ScriptEngineFactory spi = (ScriptEngineFactory)obj; try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } for (ScriptEngineFactory spi : engineSpis) { List names = null; try { names = spi.getNames(); } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } if (names != null) { for (String name : names) { if (shortName.equals(name)) { try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } } } } return null; } /** * Look up and create a ScriptEngine for a given extension. The algorithm * used by getEngineByName is used except that the search starts * by looking for a ScriptEngineFactory registered to handle the * given extension using registerEngineExtension. * @param extension The given extension * @return The engine to handle scripts with this extension. Returns null * if not found. * @throws NullPointerException if extension is null. */ public ScriptEngine getEngineByExtension(String extension) { if (extension == null) throw new NullPointerException(); //look for registered extension first Object obj; if (null != (obj = extensionAssociations.get(extension))) { ScriptEngineFactory spi = (ScriptEngineFactory)obj; try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } for (ScriptEngineFactory spi : engineSpis) { List exts = null; try { exts = spi.getExtensions(); } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } if (exts == null) continue; for (String ext : exts) { if (extension.equals(ext)) { try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } } } return null; } /** * Look up and create a ScriptEngine for a given mime type. The algorithm * used by getEngineByName is used except that the search starts * by looking for a ScriptEngineFactory registered to handle the * given mime type using registerEngineMimeType. * @param mimeType The given mime type * @return The engine to handle scripts with this mime type. Returns null * if not found. * @throws NullPointerException if mimeType is null. */ public ScriptEngine getEngineByMimeType(String mimeType) { if (mimeType == null) throw new NullPointerException(); //look for registered types first Object obj; if (null != (obj = mimeTypeAssociations.get(mimeType))) { ScriptEngineFactory spi = (ScriptEngineFactory)obj; try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } for (ScriptEngineFactory spi : engineSpis) { List types = null; try { types = spi.getMimeTypes(); } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } if (types == null) continue; for (String type : types) { if (mimeType.equals(type)) { try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } } } return null; } /** * Returns an array whose elements are instances of all the ScriptEngineFactory classes * found by the discovery mechanism. * @return List of all discovered ScriptEngineFactorys. */ public List getEngineFactories() { List res = new ArrayList(engineSpis.size()); for (ScriptEngineFactory spi : engineSpis) { res.add(spi); } return Collections.unmodifiableList(res); } /** * Registers a ScriptEngineFactory to handle a language * name. Overrides any such association found using the Discovery mechanism. * @param name The name to be associated with the ScriptEngineFactory. * @param factory The class to associate with the given name. * @throws NullPointerException if any of the parameters is null. */ public void registerEngineName(String name, ScriptEngineFactory factory) { if (name == null || factory == null) throw new NullPointerException(); nameAssociations.put(name, factory); } /** * Registers a ScriptEngineFactory to handle a mime type. * Overrides any such association found using the Discovery mechanism. * * @param type The mime type to be associated with the * ScriptEngineFactory. * * @param factory The class to associate with the given mime type. * @throws NullPointerException if any of the parameters is null. */ public void registerEngineMimeType(String type, ScriptEngineFactory factory) { if (type == null || factory == null) throw new NullPointerException(); mimeTypeAssociations.put(type, factory); } /** * Registers a ScriptEngineFactory to handle an extension. * Overrides any such association found using the Discovery mechanism. * * @param extension The extension type to be associated with the * ScriptEngineFactory. * @param factory The class to associate with the given extension. * @throws NullPointerException if any of the parameters is null. */ public void registerEngineExtension(String extension, ScriptEngineFactory factory) { if (extension == null || factory == null) throw new NullPointerException(); extensionAssociations.put(extension, factory); } /** Set of script engine factories discovered. */ private HashSet engineSpis; /** Map of engine name to script engine factory. */ private HashMap nameAssociations; /** Map of script file extension to script engine factory. */ private HashMap extensionAssociations; /** Map of script script MIME type to script engine factory. */ private HashMap mimeTypeAssociations; /** Global bindings associated with script engines created by this manager. */ private Bindings globalScope; private boolean canCallerAccessLoader(ClassLoader loader) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader callerLoader = getCallerClassLoader(); if (callerLoader != null) { if (loader != callerLoader || !isAncestor(loader, callerLoader)) { try { sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); } catch (SecurityException exp) { if (DEBUG) exp.printStackTrace(); return false; } } // else fallthru.. } // else fallthru.. } // else fallthru.. return true; } // Note that this code is same as ClassLoader.getCallerClassLoader(). // But, that method is package private and hence we can't call here. private ClassLoader getCallerClassLoader() { Class caller = Reflection.getCallerClass(3); if (caller == null) { return null; } return caller.getClassLoader(); } // is cl1 ancestor of cl2? private boolean isAncestor(ClassLoader cl1, ClassLoader cl2) { do { cl2 = cl2.getParent(); if (cl1 == cl2) return true; } while (cl2 != null); return false; } }