Path: blob/master/src/java.scripting/share/classes/javax/script/ScriptEngineManager.java
41153 views
/*1* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.script;26import java.util.*;27import java.security.*;28import java.util.ServiceLoader;29import java.util.ServiceConfigurationError;30import java.util.function.Function;31import java.util.stream.Stream;3233/**34* The <code>ScriptEngineManager</code> implements a discovery and instantiation35* mechanism for <code>ScriptEngine</code> classes and also maintains a36* collection of key/value pairs storing state shared by all engines created37* by the Manager. This class uses the service provider mechanism described in the38* {@link java.util.ServiceLoader} class to enumerate all the39* implementations of <code>ScriptEngineFactory</code>. <br><br>40* The <code>ScriptEngineManager</code> provides a method to return a list of all these factories41* as well as utility methods which look up factories on the basis of language name, file extension42* and mime type.43* <p>44* The <code>Bindings</code> of key/value pairs, referred to as the "Global Scope" maintained45* by the manager is available to all instances of <code>ScriptEngine</code> created46* by the <code>ScriptEngineManager</code>. The values in the <code>Bindings</code> are47* generally exposed in all scripts.48*49* @author Mike Grogan50* @author A. Sundararajan51* @since 1.652*/53public class ScriptEngineManager {54private static final boolean DEBUG = false;55/**56* The effect of calling this constructor is the same as calling57* <code>ScriptEngineManager(Thread.currentThread().getContextClassLoader())</code>.58*59* @see java.lang.Thread#getContextClassLoader60*/61public ScriptEngineManager() {62this(Thread.currentThread().getContextClassLoader());63}6465/**66* This constructor loads the implementations of67* <code>ScriptEngineFactory</code> visible to the given68* <code>ClassLoader</code> using the service provider mechanism.<br><br>69* If loader is <code>null</code>, the script engine factories that are70* bundled with the platform are loaded. <br>71*72* @param loader ClassLoader used to discover script engine factories.73*/74public ScriptEngineManager(ClassLoader loader) {75initEngines(loader);76}7778private ServiceLoader<ScriptEngineFactory> getServiceLoader(final ClassLoader loader) {79if (loader != null) {80return ServiceLoader.load(ScriptEngineFactory.class, loader);81} else {82return ServiceLoader.loadInstalled(ScriptEngineFactory.class);83}84}8586private void initEngines(final ClassLoader loader) {87Iterator<ScriptEngineFactory> itr;88try {89@SuppressWarnings("removal")90var sl = AccessController.doPrivileged(91(PrivilegedAction<ServiceLoader<ScriptEngineFactory>>)() -> getServiceLoader(loader));92itr = sl.iterator();93} catch (ServiceConfigurationError err) {94reportException("Can't find ScriptEngineFactory providers: ", err);95// do not throw any exception here. user may want to96// manage his/her own factories using this manager97// by explicit registratation (by registerXXX) methods.98return;99}100101try {102while (itr.hasNext()) {103try {104ScriptEngineFactory fact = itr.next();105engineSpis.add(fact);106} catch (ServiceConfigurationError err) {107reportException("ScriptEngineManager providers.next(): ", err);108// one factory failed, but check other factories...109}110}111} catch (ServiceConfigurationError err) {112reportException("ScriptEngineManager providers.hasNext(): ", err);113// do not throw any exception here. user may want to114// manage his/her own factories using this manager115// by explicit registratation (by registerXXX) methods.116}117}118119/**120* <code>setBindings</code> stores the specified <code>Bindings</code>121* in the <code>globalScope</code> field. ScriptEngineManager sets this122* <code>Bindings</code> as global bindings for <code>ScriptEngine</code>123* objects created by it.124*125* @param bindings The specified <code>Bindings</code>126* @throws IllegalArgumentException if bindings is null.127*/128public void setBindings(Bindings bindings) {129if (bindings == null) {130throw new IllegalArgumentException("Global scope cannot be null.");131}132133globalScope = bindings;134}135136/**137* <code>getBindings</code> returns the value of the <code>globalScope</code> field.138* ScriptEngineManager sets this <code>Bindings</code> as global bindings for139* <code>ScriptEngine</code> objects created by it.140*141* @return The globalScope field.142*/143public Bindings getBindings() {144return globalScope;145}146147/**148* Sets the specified key/value pair in the Global Scope.149* @param key Key to set150* @param value Value to set.151* @throws NullPointerException if key is null.152* @throws IllegalArgumentException if key is empty string.153*/154public void put(String key, Object value) {155globalScope.put(key, value);156}157158/**159* Gets the value for the specified key in the Global Scope160* @param key The key whose value is to be returned.161* @return The value for the specified key.162*/163public Object get(String key) {164return globalScope.get(key);165}166167/**168* Looks up and creates a <code>ScriptEngine</code> for a given name.169* The algorithm first searches for a <code>ScriptEngineFactory</code> that has been170* registered as a handler for the specified name using the <code>registerEngineName</code>171* method.172* <br><br> If one is not found, it searches the set of <code>ScriptEngineFactory</code> instances173* stored by the constructor for one with the specified name. If a <code>ScriptEngineFactory</code>174* is found by either method, it is used to create instance of <code>ScriptEngine</code>.175* @param shortName The short name of the <code>ScriptEngine</code> implementation.176* returned by the <code>getNames</code> method of its <code>ScriptEngineFactory</code>.177* @return A <code>ScriptEngine</code> created by the factory located in the search. Returns null178* if no such factory was found. The <code>ScriptEngineManager</code> sets its own <code>globalScope</code>179* <code>Bindings</code> as the <code>GLOBAL_SCOPE</code> <code>Bindings</code> of the newly180* created <code>ScriptEngine</code>.181* @throws NullPointerException if shortName is null.182*/183public ScriptEngine getEngineByName(String shortName) {184return getEngineBy(shortName, nameAssociations, ScriptEngineFactory::getNames);185}186187/**188* Look up and create a <code>ScriptEngine</code> for a given extension. The algorithm189* used by <code>getEngineByName</code> is used except that the search starts190* by looking for a <code>ScriptEngineFactory</code> registered to handle the191* given extension using <code>registerEngineExtension</code>.192* @param extension The given extension193* @return The engine to handle scripts with this extension. Returns <code>null</code>194* if not found.195* @throws NullPointerException if extension is null.196*/197public ScriptEngine getEngineByExtension(String extension) {198return getEngineBy(extension, extensionAssociations, ScriptEngineFactory::getExtensions);199}200201/**202* Look up and create a <code>ScriptEngine</code> for a given mime type. The algorithm203* used by <code>getEngineByName</code> is used except that the search starts204* by looking for a <code>ScriptEngineFactory</code> registered to handle the205* given mime type using <code>registerEngineMimeType</code>.206* @param mimeType The given mime type207* @return The engine to handle scripts with this mime type. Returns <code>null</code>208* if not found.209* @throws NullPointerException if mimeType is null.210*/211public ScriptEngine getEngineByMimeType(String mimeType) {212return getEngineBy(mimeType, mimeTypeAssociations, ScriptEngineFactory::getMimeTypes);213}214215private ScriptEngine getEngineBy(String selector, Map<String, ScriptEngineFactory> associations,216Function<ScriptEngineFactory, List<String>> valuesFn)217{218Objects.requireNonNull(selector);219Stream<ScriptEngineFactory> spis = Stream.concat(220//look for registered types first221Stream.ofNullable(associations.get(selector)),222223engineSpis.stream().filter(spi -> {224try {225List<String> matches = valuesFn.apply(spi);226return matches != null && matches.contains(selector);227} catch (Exception exp) {228debugPrint(exp);229return false;230}231})232);233return spis234.map(spi -> {235try {236ScriptEngine engine = spi.getScriptEngine();237engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);238return engine;239} catch (Exception exp) {240debugPrint(exp);241return null;242}243})244.filter(Objects::nonNull)245.findFirst()246.orElse(null);247}248249private static void reportException(String msg, Throwable exp) {250System.err.println(msg + exp.getMessage());251debugPrint(exp);252}253254private static void debugPrint(Throwable exp) {255if (DEBUG) {256exp.printStackTrace();257}258}259260/**261* Returns a list whose elements are instances of all the <code>ScriptEngineFactory</code> classes262* found by the discovery mechanism.263* @return List of all discovered <code>ScriptEngineFactory</code>s.264*/265public List<ScriptEngineFactory> getEngineFactories() {266return List.copyOf(engineSpis);267}268269/**270* Registers a <code>ScriptEngineFactory</code> to handle a language271* name. Overrides any such association found using the Discovery mechanism.272* @param name The name to be associated with the <code>ScriptEngineFactory</code>.273* @param factory The class to associate with the given name.274* @throws NullPointerException if any of the parameters is null.275*/276public void registerEngineName(String name, ScriptEngineFactory factory) {277associateFactory(nameAssociations, name, factory);278}279280/**281* Registers a <code>ScriptEngineFactory</code> to handle a mime type.282* Overrides any such association found using the Discovery mechanism.283*284* @param type The mime type to be associated with the285* <code>ScriptEngineFactory</code>.286*287* @param factory The class to associate with the given mime type.288* @throws NullPointerException if any of the parameters is null.289*/290public void registerEngineMimeType(String type, ScriptEngineFactory factory) {291associateFactory(mimeTypeAssociations, type, factory);292}293294/**295* Registers a <code>ScriptEngineFactory</code> to handle an extension.296* Overrides any such association found using the Discovery mechanism.297*298* @param extension The extension type to be associated with the299* <code>ScriptEngineFactory</code>.300* @param factory The class to associate with the given extension.301* @throws NullPointerException if any of the parameters is null.302*/303public void registerEngineExtension(String extension, ScriptEngineFactory factory) {304associateFactory(extensionAssociations, extension, factory);305}306307private static void associateFactory(Map<String, ScriptEngineFactory> associations, String association,308ScriptEngineFactory factory)309{310if (association == null || factory == null) throw new NullPointerException();311associations.put(association, factory);312}313314private static final Comparator<ScriptEngineFactory> COMPARATOR = Comparator.comparing(315ScriptEngineFactory::getEngineName,316Comparator.nullsLast(Comparator.naturalOrder())317);318319/** Set of script engine factories discovered. */320private final TreeSet<ScriptEngineFactory> engineSpis = new TreeSet<>(COMPARATOR);321322/** Map of engine name to script engine factory. */323private final HashMap<String, ScriptEngineFactory> nameAssociations = new HashMap<>();324325/** Map of script file extension to script engine factory. */326private final HashMap<String, ScriptEngineFactory> extensionAssociations = new HashMap<>();327328/** Map of script MIME type to script engine factory. */329private final HashMap<String, ScriptEngineFactory> mimeTypeAssociations = new HashMap<>();330331/** Global bindings associated with script engines created by this manager. */332private Bindings globalScope = new SimpleBindings();333}334335336