| /* |
| * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package apple.applescript; |
| |
| import java.io.*; |
| import java.nio.file.Files; |
| import java.util.*; |
| import java.util.Map.Entry; |
| |
| import javax.script.*; |
| |
| /** |
| * AppleScriptEngine implements JSR 223 for AppleScript on Mac OS X |
| */ |
| public class AppleScriptEngine implements ScriptEngine { |
| private static native void initNative(); |
| |
| private static native long createContextFrom(final Object object); |
| private static native Object createObjectFrom(final long context); |
| private static native void disposeContext(final long context); |
| |
| private static native long evalScript(final String script, long contextptr); |
| private static native long evalScriptFromURL(final String filename, long contextptr); |
| |
| static { |
| System.loadLibrary("AppleScriptEngine"); |
| initNative(); |
| TRACE("<static-init>"); |
| } |
| |
| static void checkSecurity() { |
| final SecurityManager securityManager = System.getSecurityManager(); |
| if (securityManager != null) securityManager.checkExec("/usr/bin/osascript"); |
| } |
| |
| static void TRACE(final String str) { |
| // System.out.println(AppleScriptEngine.class.getName() + "." + str); |
| } |
| |
| /** |
| * Accessor for the ScriptEngine's long name variable |
| * @return the long name of the ScriptEngine |
| */ |
| protected static String getEngine() { |
| TRACE("getEngine()"); |
| return AppleScriptEngineFactory.ENGINE_NAME; |
| } |
| |
| /** |
| * Accessor for the ScriptEngine's version |
| * @return the version of the ScriptEngine |
| */ |
| protected static String getEngineVersion() { |
| TRACE("getEngineVersion()"); |
| return AppleScriptEngineFactory.ENGINE_VERSION; |
| } |
| |
| /** |
| * Accessor for the ScriptEngine's short name |
| * @return the short name of the ScriptEngine |
| */ |
| protected static String getName() { |
| TRACE("getName()"); |
| return AppleScriptEngineFactory.ENGINE_SHORT_NAME; |
| } |
| |
| /** |
| * Accessor for the ScriptEngine's supported language name |
| * @return the language the ScriptEngine supports |
| */ |
| protected static String getLanguage() { |
| TRACE("getLanguage()"); |
| return AppleScriptEngineFactory.LANGUAGE; |
| } |
| |
| /** |
| * The no argument constructor sets up the object with default members, |
| * a factory for the engine and a fresh context. |
| * @see com.apple.applescript.AppleScriptEngine#init() |
| */ |
| public AppleScriptEngine() { |
| TRACE("<ctor>()"); |
| // set our parent factory to be a new factory |
| factory = AppleScriptEngineFactory.getFactory(); |
| |
| // set up our noarg bindings |
| setContext(new SimpleScriptContext()); |
| put(ARGV, ""); |
| |
| init(); |
| } |
| |
| /** |
| * All AppleScriptEngines share the same ScriptEngineFactory |
| */ |
| private final ScriptEngineFactory factory; |
| |
| /** |
| * The local context for the AppleScriptEngine |
| */ |
| private ScriptContext context; |
| |
| /** |
| * The constructor taking a factory as an argument sets the parent factory for |
| * this engine to be the passed factory, and sets up the engine with a fresh context |
| * @param factory |
| * @see com.apple.applescript.AppleScriptEngine#init() |
| */ |
| public AppleScriptEngine(final ScriptEngineFactory factory) { |
| // inherit the factory passed to us |
| this.factory = factory; |
| |
| // set up our noarg bindings |
| setContext(new SimpleScriptContext()); |
| put(ARGV, ""); |
| |
| init(); |
| } |
| |
| /** |
| * The initializer populates the local context with some useful predefined variables: |
| * <ul><li><code>javax_script_language_version</code> - the version of AppleScript that the AppleScriptEngine supports.</li> |
| * <li><code>javax_script_language</code> - "AppleScript" -- the language supported by the AppleScriptEngine.</li> |
| * <li><code>javax_script_engine</code> - "AppleScriptEngine" -- the name of the ScriptEngine.</li> |
| * <li><code>javax_script_engine_version</code> - the version of the AppleScriptEngine</li> |
| * <li><code>javax_script_argv</code> - "" -- AppleScript does not take arguments from the command line</li> |
| * <li><code>javax_script_filename</code> - "" -- the currently executing filename</li> |
| * <li><code>javax_script_name</code> - "AppleScriptEngine" -- the short name of the AppleScriptEngine</li> |
| * <li><code>THREADING</code> - null -- the AppleScriptEngine does not support concurrency, you will have to implement thread-safeness yourself.</li></ul> |
| */ |
| private void init() { |
| TRACE("init()"); |
| // set up our context |
| /* TODO -- name of current executable? bad java documentation at: |
| * http://docs.oracle.com/javase/6/docs/api/javax/script/ScriptEngine.html#FILENAME */ |
| put(ScriptEngine.FILENAME, ""); |
| put(ScriptEngine.ENGINE, getEngine()); |
| put(ScriptEngine.ENGINE_VERSION, getEngineVersion()); |
| put(ScriptEngine.NAME, getName()); |
| put(ScriptEngine.LANGUAGE, getLanguage()); |
| put(ScriptEngine.LANGUAGE_VERSION, getLanguageVersion()); |
| |
| // TODO -- for now, err on the side of caution and say that we are NOT thread-safe |
| put("THREADING", null); |
| } |
| |
| /** |
| * Uses the AppleScriptEngine to get the local AppleScript version |
| * @return the version of AppleScript running on the system |
| */ |
| protected String getLanguageVersion() { |
| TRACE("AppleScriptEngine.getLanguageVersion()"); |
| try { |
| final Object result = eval("get the version of AppleScript"); |
| if (result instanceof String) return (String)result; |
| } catch (final ScriptException e) { e.printStackTrace(); } |
| return "unknown"; |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Returns the factory parent of this AppleScriptEngine |
| */ |
| public ScriptEngineFactory getFactory() { |
| return factory; |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Return the engine's context |
| * @return this ScriptEngine's context |
| */ |
| public ScriptContext getContext() { |
| return context; |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Set a new context for the engine |
| * @param context the new context to install in the engine |
| */ |
| public void setContext(final ScriptContext context) { |
| this.context = context; |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Create and return a new set of simple bindings. |
| * @return a new and empty set of bindings |
| */ |
| public Bindings createBindings() { |
| return new SimpleBindings(); |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Return the engines bindings for the context indicated by the argument. |
| * @param scope contextual scope to return. |
| * @return the bindings in the engine for the scope indicated by the parameter |
| */ |
| public Bindings getBindings(final int scope) { |
| return context.getBindings(scope); |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Sets the bindings for the indicated scope |
| * @param bindings a set of bindings to assign to the engine |
| * @param scope the scope that the passed bindings should be assigned to |
| */ |
| public void setBindings(final Bindings bindings, final int scope) { |
| context.setBindings(bindings, scope); |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Insert a key and value into the engine's bindings (scope: engine) |
| * @param key the key of the pair |
| * @param value the value of the pair |
| */ |
| public void put(final String key, final Object value) { |
| getBindings(ScriptContext.ENGINE_SCOPE).put(key, value); |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Get a value from the engine's bindings using a key (scope: engine) |
| * @param key the key of the pair |
| * @return the value of the pair |
| */ |
| public Object get(final String key) { |
| return getBindings(ScriptContext.ENGINE_SCOPE).get(key); |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Passes the Reader argument, as well as the engine's context to a lower evaluation function.<br /> |
| * Prefers FileReader or BufferedReader wrapping FileReader as argument. |
| * @param reader a Reader to AppleScript source or compiled AppleScript |
| * @return an Object corresponding to the return value of the script |
| * @see com.apple.applescript.AppleScriptEngine#eval(Reader, ScriptContext) |
| */ |
| public Object eval(final Reader reader) throws ScriptException { |
| return eval(reader, getContext()); |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Uses the passed bindings as the context for executing the passed script. |
| * @param reader a stream to AppleScript source or compiled AppleScript |
| * @param bindings a Bindings object representing the contexts to execute inside |
| * @return the return value of the script |
| * @see com.apple.applescript.AppleScriptEngine#eval(Reader, ScriptContext) |
| */ |
| public Object eval(final Reader reader, final Bindings bindings) throws ScriptException { |
| final Bindings tmp = getContext().getBindings(ScriptContext.ENGINE_SCOPE); |
| getContext().setBindings(bindings, ScriptContext.ENGINE_SCOPE); |
| final Object retval = eval(reader); |
| getContext().setBindings(tmp, ScriptContext.ENGINE_SCOPE); |
| return retval; |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * This function can execute either AppleScript source or compiled AppleScript and functions by writing the |
| * contents of the Reader to a temporary file and then executing it with the engine's context. |
| * @param reader |
| * @param scriptContext |
| * @return an Object corresponding to the return value of the script |
| */ |
| public Object eval(final Reader reader, final ScriptContext context) throws ScriptException { |
| checkSecurity(); |
| |
| // write our passed reader to a temporary file |
| File tmpfile; |
| FileWriter tmpwrite; |
| try { |
| tmpfile = Files.createTempFile("AppleScriptEngine.", ".scpt").toFile(); |
| tmpwrite = new FileWriter(tmpfile); |
| |
| // read in our input and write directly to tmpfile |
| /* TODO -- this may or may not be avoidable for certain Readers, |
| * if a filename can be grabbed, it would be faster to get that and |
| * use the underlying file than writing a temp file. |
| */ |
| int data; |
| while ((data = reader.read()) != -1) { |
| tmpwrite.write(data); |
| } |
| tmpwrite.close(); |
| |
| // set up our context business |
| final long contextptr = scriptContextToNSDictionary(context); |
| try { |
| final long retCtx = evalScriptFromURL("file://" + tmpfile.getCanonicalPath(), contextptr); |
| Object retVal = (retCtx == 0) ? null : createObjectFrom(retCtx); |
| disposeContext(retCtx); |
| return retVal; |
| } finally { |
| disposeContext(contextptr); |
| tmpfile.delete(); |
| } |
| } catch (final IOException e) { |
| throw new ScriptException(e); |
| } |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Evaluate an AppleScript script passed as a source string. Using the engine's built in context. |
| * @param script the string to execute. |
| * @return an Object representing the return value of the script |
| * @see com.apple.applescript.AppleScriptEngine#eval(String, ScriptContext) |
| */ |
| public Object eval(final String script) throws ScriptException { |
| return eval(script, getContext()); |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent<br /> |
| * Evaluate an AppleScript script passed as a source string with a custom ScriptContext. |
| * @param script the AppleScript source to compile and execute. |
| * @param scriptContext the context to execute the script under |
| * @see com.apple.applescript.AppleScriptEngine#eval(String, ScriptContext) |
| */ |
| public Object eval(final String script, final Bindings bindings) throws ScriptException { |
| final Bindings tmp = getContext().getBindings(ScriptContext.ENGINE_SCOPE); |
| getContext().setBindings(bindings, ScriptContext.ENGINE_SCOPE); |
| |
| final Object retval = eval(script); |
| getContext().setBindings(tmp, ScriptContext.ENGINE_SCOPE); |
| |
| return retval; |
| } |
| |
| /** |
| * Implementation required by ScriptEngine parent |
| * @param script |
| * @param scriptContext |
| */ |
| public Object eval(final String script, final ScriptContext context) throws ScriptException { |
| checkSecurity(); |
| final long ctxPtr = scriptContextToNSDictionary(context); |
| try { |
| final long retCtx = evalScript(script, ctxPtr); |
| Object retVal = (retCtx == 0) ? null : createObjectFrom(retCtx); |
| disposeContext(retCtx); |
| return retVal; |
| } finally { |
| disposeContext(ctxPtr); |
| } |
| } |
| |
| /** |
| * Converts a ScriptContext into an NSDictionary |
| * @param context ScriptContext for the engine |
| * @return a pointer to an NSDictionary |
| */ |
| private long scriptContextToNSDictionary(final ScriptContext context) throws ScriptException { |
| final Map<String, Object> contextAsMap = new HashMap<String, Object>(); |
| for (final Entry<String, Object> e : context.getBindings(ScriptContext.ENGINE_SCOPE).entrySet()) { |
| contextAsMap.put(e.getKey().replaceAll("\\.", "_"), e.getValue()); |
| } |
| return createContextFrom(contextAsMap); |
| } |
| } |