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