/* * 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 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("()"); // 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: * */ 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
* Returns the factory parent of this AppleScriptEngine */ public ScriptEngineFactory getFactory() { return factory; } /** * Implementation required by ScriptEngine parent
* Return the engine's context * @return this ScriptEngine's context */ public ScriptContext getContext() { return context; } /** * Implementation required by ScriptEngine parent
* 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
* 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
* 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
* 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
* 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
* 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
* Passes the Reader argument, as well as the engine's context to a lower evaluation function.
* 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
* 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
* 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
* 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
* 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 contextAsMap = new HashMap(); for (final Entry e : context.getBindings(ScriptContext.ENGINE_SCOPE).entrySet()) { contextAsMap.put(e.getKey().replaceAll("\\.", "_"), e.getValue()); } return createContextFrom(contextAsMap); } }