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