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 }