1 /* 2 * Copyright (c) 2011, 2012, 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 apple.applescript; 27 28 import java.io.*; 29 import java.nio.file.Files; 30 import java.util.*; 31 import java.util.Map.Entry; 32 33 import javax.script.*; 34 35 /** 36 * AppleScriptEngine implements JSR 223 for AppleScript on Mac OS X 37 */ 38 public class AppleScriptEngine implements ScriptEngine { 39 private static native void initNative(); 40 41 private static native long createContextFrom(final Object object); 42 private static native Object createObjectFrom(final long context); 43 private static native void disposeContext(final long context); 44 45 private static native long evalScript(final String script, long contextptr); 46 private static native long evalScriptFromURL(final String filename, long contextptr); 47 48 static { 49 System.loadLibrary("AppleScriptEngine"); 50 initNative(); 51 TRACE("<static-init>"); 52 } 53 54 static void checkSecurity() { 55 final SecurityManager securityManager = System.getSecurityManager(); 56 if (securityManager != null) securityManager.checkExec("/usr/bin/osascript"); 57 } 58 59 static void TRACE(final String str) { 60 // System.out.println(AppleScriptEngine.class.getName() + "." + str); 61 } 62 63 /** 64 * Accessor for the ScriptEngine's long name variable 65 * @return the long name of the ScriptEngine 66 */ 67 protected static String getEngine() { 68 TRACE("getEngine()"); 69 return AppleScriptEngineFactory.ENGINE_NAME; 70 } 71 72 /** 73 * Accessor for the ScriptEngine's version 74 * @return the version of the ScriptEngine 75 */ 76 protected static String getEngineVersion() { 77 TRACE("getEngineVersion()"); 78 return AppleScriptEngineFactory.ENGINE_VERSION; 79 } 80 81 /** 82 * Accessor for the ScriptEngine's short name 83 * @return the short name of the ScriptEngine 84 */ 85 protected static String getName() { 86 TRACE("getName()"); 87 return AppleScriptEngineFactory.ENGINE_SHORT_NAME; 88 } 89 90 /** 91 * Accessor for the ScriptEngine's supported language name 92 * @return the language the ScriptEngine supports 93 */ 94 protected static String getLanguage() { 95 TRACE("getLanguage()"); 96 return AppleScriptEngineFactory.LANGUAGE; 97 } 98 99 /** 100 * The no argument constructor sets up the object with default members, 101 * a factory for the engine and a fresh context. 102 * @see com.apple.applescript.AppleScriptEngine#init() 103 */ 104 public AppleScriptEngine() { 105 TRACE("<ctor>()"); 106 // set our parent factory to be a new factory 107 factory = AppleScriptEngineFactory.getFactory(); 108 109 // set up our noarg bindings 110 setContext(new SimpleScriptContext()); 111 put(ARGV, ""); 112 113 init(); 114 } 115 116 /** 117 * All AppleScriptEngines share the same ScriptEngineFactory 118 */ 119 private final ScriptEngineFactory factory; 120 121 /** 122 * The local context for the AppleScriptEngine 123 */ 124 private ScriptContext context; 125 126 /** 127 * The constructor taking a factory as an argument sets the parent factory for 128 * this engine to be the passed factory, and sets up the engine with a fresh context 129 * @param factory 130 * @see com.apple.applescript.AppleScriptEngine#init() 131 */ 132 public AppleScriptEngine(final ScriptEngineFactory factory) { 133 // inherit the factory passed to us 134 this.factory = factory; 135 136 // set up our noarg bindings 137 setContext(new SimpleScriptContext()); 138 put(ARGV, ""); 139 140 init(); 141 } 142 143 /** 144 * The initializer populates the local context with some useful predefined variables: 145 * <ul><li><code>javax_script_language_version</code> - the version of AppleScript that the AppleScriptEngine supports.</li> 146 * <li><code>javax_script_language</code> - "AppleScript" -- the language supported by the AppleScriptEngine.</li> 147 * <li><code>javax_script_engine</code> - "AppleScriptEngine" -- the name of the ScriptEngine.</li> 148 * <li><code>javax_script_engine_version</code> - the version of the AppleScriptEngine</li> 149 * <li><code>javax_script_argv</code> - "" -- AppleScript does not take arguments from the command line</li> 150 * <li><code>javax_script_filename</code> - "" -- the currently executing filename</li> 151 * <li><code>javax_script_name</code> - "AppleScriptEngine" -- the short name of the AppleScriptEngine</li> 152 * <li><code>THREADING</code> - null -- the AppleScriptEngine does not support concurrency, you will have to implement thread-safeness yourself.</li></ul> 153 */ 154 private void init() { 155 TRACE("init()"); 156 // set up our context 157 /* TODO -- name of current executable? bad java documentation at: 158 * http://docs.oracle.com/javase/6/docs/api/javax/script/ScriptEngine.html#FILENAME */ 159 put(ScriptEngine.FILENAME, ""); 160 put(ScriptEngine.ENGINE, getEngine()); 161 put(ScriptEngine.ENGINE_VERSION, getEngineVersion()); 162 put(ScriptEngine.NAME, getName()); 163 put(ScriptEngine.LANGUAGE, getLanguage()); 164 put(ScriptEngine.LANGUAGE_VERSION, getLanguageVersion()); 165 166 // TODO -- for now, err on the side of caution and say that we are NOT thread-safe 167 put("THREADING", null); 168 } 169 170 /** 171 * Uses the AppleScriptEngine to get the local AppleScript version 172 * @return the version of AppleScript running on the system 173 */ 174 protected String getLanguageVersion() { 175 TRACE("AppleScriptEngine.getLanguageVersion()"); 176 try { 177 final Object result = eval("get the version of AppleScript"); 178 if (result instanceof String) return (String)result; 179 } catch (final ScriptException e) { e.printStackTrace(); } 180 return "unknown"; 181 } 182 183 /** 184 * Implementation required by ScriptEngine parent<br /> 185 * Returns the factory parent of this AppleScriptEngine 186 */ 187 public ScriptEngineFactory getFactory() { 188 return factory; 189 } 190 191 /** 192 * Implementation required by ScriptEngine parent<br /> 193 * Return the engine's context 194 * @return this ScriptEngine's context 195 */ 196 public ScriptContext getContext() { 197 return context; 198 } 199 200 /** 201 * Implementation required by ScriptEngine parent<br /> 202 * Set a new context for the engine 203 * @param context the new context to install in the engine 204 */ 205 public void setContext(final ScriptContext context) { 206 this.context = context; 207 } 208 209 /** 210 * Implementation required by ScriptEngine parent<br /> 211 * Create and return a new set of simple bindings. 212 * @return a new and empty set of bindings 213 */ 214 public Bindings createBindings() { 215 return new SimpleBindings(); 216 } 217 218 /** 219 * Implementation required by ScriptEngine parent<br /> 220 * Return the engines bindings for the context indicated by the argument. 221 * @param scope contextual scope to return. 222 * @return the bindings in the engine for the scope indicated by the parameter 223 */ 224 public Bindings getBindings(final int scope) { 225 return context.getBindings(scope); 226 } 227 228 /** 229 * Implementation required by ScriptEngine parent<br /> 230 * Sets the bindings for the indicated scope 231 * @param bindings a set of bindings to assign to the engine 232 * @param scope the scope that the passed bindings should be assigned to 233 */ 234 public void setBindings(final Bindings bindings, final int scope) { 235 context.setBindings(bindings, scope); 236 } 237 238 /** 239 * Implementation required by ScriptEngine parent<br /> 240 * Insert a key and value into the engine's bindings (scope: engine) 241 * @param key the key of the pair 242 * @param value the value of the pair 243 */ 244 public void put(final String key, final Object value) { 245 getBindings(ScriptContext.ENGINE_SCOPE).put(key, value); 246 } 247 248 /** 249 * Implementation required by ScriptEngine parent<br /> 250 * Get a value from the engine's bindings using a key (scope: engine) 251 * @param key the key of the pair 252 * @return the value of the pair 253 */ 254 public Object get(final String key) { 255 return getBindings(ScriptContext.ENGINE_SCOPE).get(key); 256 } 257 258 /** 259 * Implementation required by ScriptEngine parent<br /> 260 * Passes the Reader argument, as well as the engine's context to a lower evaluation function.<br /> 261 * Prefers FileReader or BufferedReader wrapping FileReader as argument. 262 * @param reader a Reader to AppleScript source or compiled AppleScript 263 * @return an Object corresponding to the return value of the script 264 * @see com.apple.applescript.AppleScriptEngine#eval(Reader, ScriptContext) 265 */ 266 public Object eval(final Reader reader) throws ScriptException { 267 return eval(reader, getContext()); 268 } 269 270 /** 271 * Implementation required by ScriptEngine parent<br /> 272 * Uses the passed bindings as the context for executing the passed script. 273 * @param reader a stream to AppleScript source or compiled AppleScript 274 * @param bindings a Bindings object representing the contexts to execute inside 275 * @return the return value of the script 276 * @see com.apple.applescript.AppleScriptEngine#eval(Reader, ScriptContext) 277 */ 278 public Object eval(final Reader reader, final Bindings bindings) throws ScriptException { 279 final Bindings tmp = getContext().getBindings(ScriptContext.ENGINE_SCOPE); 280 getContext().setBindings(bindings, ScriptContext.ENGINE_SCOPE); 281 final Object retval = eval(reader); 282 getContext().setBindings(tmp, ScriptContext.ENGINE_SCOPE); 283 return retval; 284 } 285 286 /** 287 * Implementation required by ScriptEngine parent<br /> 288 * This function can execute either AppleScript source or compiled AppleScript and functions by writing the 289 * contents of the Reader to a temporary file and then executing it with the engine's context. 290 * @param reader 291 * @param scriptContext 292 * @return an Object corresponding to the return value of the script 293 */ 294 public Object eval(final Reader reader, final ScriptContext context) throws ScriptException { 295 checkSecurity(); 296 297 // write our passed reader to a temporary file 298 File tmpfile; 299 FileWriter tmpwrite; 300 try { 301 tmpfile = Files.createTempFile("AppleScriptEngine.", ".scpt").toFile(); 302 tmpwrite = new FileWriter(tmpfile); 303 304 // read in our input and write directly to tmpfile 305 /* TODO -- this may or may not be avoidable for certain Readers, 306 * if a filename can be grabbed, it would be faster to get that and 307 * use the underlying file than writing a temp file. 308 */ 309 int data; 310 while ((data = reader.read()) != -1) { 311 tmpwrite.write(data); 312 } 313 tmpwrite.close(); 314 315 // set up our context business 316 final long contextptr = scriptContextToNSDictionary(context); 317 try { 318 final long retCtx = evalScriptFromURL("file://" + tmpfile.getCanonicalPath(), contextptr); 319 Object retVal = (retCtx == 0) ? null : createObjectFrom(retCtx); 320 disposeContext(retCtx); 321 return retVal; 322 } finally { 323 disposeContext(contextptr); 324 tmpfile.delete(); 325 } 326 } catch (final IOException e) { 327 throw new ScriptException(e); 328 } 329 } 330 331 /** 332 * Implementation required by ScriptEngine parent<br /> 333 * Evaluate an AppleScript script passed as a source string. Using the engine's built in context. 334 * @param script the string to execute. 335 * @return an Object representing the return value of the script 336 * @see com.apple.applescript.AppleScriptEngine#eval(String, ScriptContext) 337 */ 338 public Object eval(final String script) throws ScriptException { 339 return eval(script, getContext()); 340 } 341 342 /** 343 * Implementation required by ScriptEngine parent<br /> 344 * Evaluate an AppleScript script passed as a source string with a custom ScriptContext. 345 * @param script the AppleScript source to compile and execute. 346 * @param scriptContext the context to execute the script under 347 * @see com.apple.applescript.AppleScriptEngine#eval(String, ScriptContext) 348 */ 349 public Object eval(final String script, final Bindings bindings) throws ScriptException { 350 final Bindings tmp = getContext().getBindings(ScriptContext.ENGINE_SCOPE); 351 getContext().setBindings(bindings, ScriptContext.ENGINE_SCOPE); 352 353 final Object retval = eval(script); 354 getContext().setBindings(tmp, ScriptContext.ENGINE_SCOPE); 355 356 return retval; 357 } 358 359 /** 360 * Implementation required by ScriptEngine parent 361 * @param script 362 * @param scriptContext 363 */ 364 public Object eval(final String script, final ScriptContext context) throws ScriptException { 365 checkSecurity(); 366 final long ctxPtr = scriptContextToNSDictionary(context); 367 try { 368 final long retCtx = evalScript(script, ctxPtr); 369 Object retVal = (retCtx == 0) ? null : createObjectFrom(retCtx); 370 disposeContext(retCtx); 371 return retVal; 372 } finally { 373 disposeContext(ctxPtr); 374 } 375 } 376 377 /** 378 * Converts a ScriptContext into an NSDictionary 379 * @param context ScriptContext for the engine 380 * @return a pointer to an NSDictionary 381 */ 382 private long scriptContextToNSDictionary(final ScriptContext context) throws ScriptException { 383 final Map<String, Object> contextAsMap = new HashMap<String, Object>(); 384 for (final Entry<String, Object> e : context.getBindings(ScriptContext.ENGINE_SCOPE).entrySet()) { 385 contextAsMap.put(e.getKey().replaceAll("\\.", "_"), e.getValue()); 386 } 387 return createContextFrom(contextAsMap); 388 } 389 }