1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.monkeyrunner;
17
18import com.google.common.base.Predicate;
19import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableMap.Builder;
21import com.google.common.collect.Lists;
22
23import org.python.core.Py;
24import org.python.core.PyException;
25import org.python.core.PyJavaPackage;
26import org.python.core.PyList;
27import org.python.core.PyObject;
28import org.python.core.PyString;
29import org.python.core.PySystemState;
30import org.python.util.InteractiveConsole;
31import org.python.util.JLineConsole;
32import org.python.util.PythonInterpreter;
33
34import java.io.File;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.List;
39import java.util.Map;
40import java.util.Properties;
41import java.util.logging.Level;
42import java.util.logging.Logger;
43
44
45/**
46 * Runs Jython based scripts.
47 */
48public class ScriptRunner {
49    private static final Logger LOG = Logger.getLogger(MonkeyRunnerOptions.class.getName());
50
51    /** The "this" scope object for scripts. */
52    private final Object scope;
53    private final String variable;
54
55    /** Private constructor. */
56    private ScriptRunner(Object scope, String variable) {
57        this.scope = scope;
58        this.variable = variable;
59    }
60
61    /** Creates a new instance for the given scope object. */
62    public static ScriptRunner newInstance(Object scope, String variable) {
63        return new ScriptRunner(scope, variable);
64    }
65
66    /**
67     * Runs the specified Jython script. First runs the initialization script to
68     * preload the appropriate client library version.
69     *
70     * @param scriptfilename the name of the file to run.
71     * @param args the arguments passed in (excluding the filename).
72     * @param plugins a list of plugins to load.
73     * @return the error code from running the script.
74     */
75    public static int run(String executablePath, String scriptfilename,
76            Collection<String> args, Map<String,
77            Predicate<PythonInterpreter>> plugins) {
78        // Add the current directory of the script to the python.path search path.
79        File f = new File(scriptfilename);
80
81        // Adjust the classpath so jython can access the classes in the specified classpath.
82        Collection<String> classpath = Lists.newArrayList(f.getParent());
83        classpath.addAll(plugins.keySet());
84
85        String[] argv = new String[args.size() + 1];
86        argv[0] = f.getAbsolutePath();
87        int x = 1;
88        for (String arg : args) {
89            argv[x++] = arg;
90        }
91
92        initPython(executablePath, classpath, argv);
93
94        PythonInterpreter python = new PythonInterpreter();
95
96        // Now let the mains run.
97        for (Map.Entry<String, Predicate<PythonInterpreter>> entry : plugins.entrySet()) {
98            boolean success;
99            try {
100                success = entry.getValue().apply(python);
101            } catch (Exception e) {
102                LOG.log(Level.SEVERE, "Plugin Main through an exception.", e);
103                continue;
104            }
105            if (!success) {
106                LOG.severe("Plugin Main returned error for: " + entry.getKey());
107            }
108        }
109
110        // Bind __name__ to __main__ so mains will run
111        python.set("__name__", "__main__");
112        // Also find __file__
113        python.set("__file__", scriptfilename);
114
115        try {
116          python.execfile(scriptfilename);
117        } catch (PyException e) {
118          if (Py.SystemExit.equals(e.type)) {
119            // Then recover the error code so we can pass it on
120            return (Integer) e.value.__tojava__(Integer.class);
121          }
122          // Then some other kind of exception was thrown.  Log it and return error;
123          LOG.log(Level.SEVERE, "Script terminated due to an exception", e);
124          return 1;
125        }
126        return 0;
127    }
128
129    public static void runString(String executablePath, String script) {
130        initPython(executablePath);
131        PythonInterpreter python = new PythonInterpreter();
132        python.exec(script);
133    }
134
135    public static Map<String, PyObject> runStringAndGet(String executablePath,
136            String script, String... names) {
137        return runStringAndGet(executablePath, script, Arrays.asList(names));
138    }
139
140    public static Map<String, PyObject> runStringAndGet(String executablePath,
141            String script, Collection<String> names) {
142        initPython(executablePath);
143        final PythonInterpreter python = new PythonInterpreter();
144        python.exec(script);
145
146        Builder<String, PyObject> builder = ImmutableMap.builder();
147        for (String name : names) {
148            builder.put(name, python.get(name));
149        }
150        return builder.build();
151    }
152
153    private static void initPython(String executablePath) {
154        List<String> arg = Collections.emptyList();
155        initPython(executablePath, arg, new String[] {""});
156    }
157
158    private static void initPython(String executablePath,
159            Collection<String> pythonPath, String[] argv) {
160        Properties props = new Properties();
161
162        // Build up the python.path
163        StringBuilder sb = new StringBuilder();
164        sb.append(System.getProperty("java.class.path"));
165        for (String p : pythonPath) {
166            sb.append(":").append(p);
167        }
168        props.setProperty("python.path", sb.toString());
169
170        /** Initialize the python interpreter. */
171        // Default is 'message' which displays sys-package-mgr bloat
172        // Choose one of error,warning,message,comment,debug
173        props.setProperty("python.verbose", "error");
174
175        // This needs to be set for sys.executable to function properly
176        props.setProperty("python.executable", executablePath);
177
178        PythonInterpreter.initialize(System.getProperties(), props, argv);
179
180        String frameworkDir = System.getProperty("java.ext.dirs");
181        File monkeyRunnerJar = new File(frameworkDir, "monkeyrunner.jar");
182        if (monkeyRunnerJar.canRead()) {
183            PySystemState.packageManager.addJar(monkeyRunnerJar.getAbsolutePath(), false);
184        }
185    }
186
187    /**
188     * Start an interactive python interpreter.
189     */
190    public static void console(String executablePath) {
191        initPython(executablePath);
192        InteractiveConsole python = new JLineConsole();
193        python.interact();
194    }
195}
196