1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package groovy.lang;
47
48 import groovy.ui.GroovyMain;
49 import org.codehaus.groovy.control.CompilationFailedException;
50 import org.codehaus.groovy.control.CompilerConfiguration;
51 import org.codehaus.groovy.runtime.InvokerHelper;
52
53 import java.io.ByteArrayInputStream;
54 import java.io.File;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.lang.reflect.Constructor;
58 import java.security.AccessController;
59 import java.security.PrivilegedAction;
60 import java.security.PrivilegedActionException;
61 import java.security.PrivilegedExceptionAction;
62 import java.util.List;
63
64 /***
65 * Represents a groovy shell capable of running arbitrary groovy scripts
66 *
67 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
68 * @author Guillaume Laforge
69 * @version $Revision: 1.37 $
70 */
71 public class GroovyShell extends GroovyObjectSupport {
72 public static final String[] EMPTY_ARGS = {
73 };
74
75 private GroovyClassLoader loader;
76 private Binding context;
77 private int counter;
78
79 public static void main(String[] args) {
80 GroovyMain.main(args);
81 }
82
83 public GroovyShell() {
84 this(null, new Binding());
85 }
86
87 public GroovyShell(Binding binding) {
88 this(null, binding);
89 }
90
91 public GroovyShell(CompilerConfiguration config) {
92 this(new Binding(), config);
93 }
94
95 public GroovyShell(Binding binding, CompilerConfiguration config) {
96 this(null, binding, config);
97 }
98
99 public GroovyShell(ClassLoader parent, Binding binding) {
100 this(parent, binding, null);
101 }
102
103 public GroovyShell(ClassLoader parent) {
104 this(parent, new Binding(), null);
105 }
106
107 public GroovyShell(final ClassLoader parent, Binding binding, final CompilerConfiguration config) {
108 this.loader =
109 (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
110 public Object run() {
111 ClassLoader pcl = parent;
112 if (pcl == null) {
113 pcl = Thread.currentThread().getContextClassLoader();
114 if (pcl == null) {
115 pcl = GroovyShell.class.getClassLoader();
116 }
117 }
118 return new GroovyClassLoader(pcl, (config == null) ? new CompilerConfiguration() : config);
119 }
120 });
121 this.context = binding;
122 }
123
124 /***
125 * Creates a child shell using a new ClassLoader which uses the parent shell's
126 * class loader as its parent
127 *
128 * @param shell is the parent shell used for the variable bindings and the parent class loader
129 */
130 public GroovyShell(GroovyShell shell) {
131 this(shell.loader, shell.context);
132 }
133
134 public Binding getContext() {
135 return context;
136 }
137
138 public Object getProperty(String property) {
139 Object answer = getVariable(property);
140 if (answer == null) {
141 answer = super.getProperty(property);
142 }
143 return answer;
144 }
145
146 public void setProperty(String property, Object newValue) {
147 setVariable(property, newValue);
148 try {
149 super.setProperty(property, newValue);
150 } catch (GroovyRuntimeException e) {
151
152 }
153 }
154
155 /***
156 * A helper method which runs the given script file with the given command line arguments
157 *
158 * @param scriptFile the file of the script to run
159 * @param list the command line arguments to pass in
160 */
161 public void run(File scriptFile, List list) throws CompilationFailedException, IOException {
162 String[] args = new String[list.size()];
163 run(scriptFile, (String[]) list.toArray(args));
164 }
165
166 /***
167 * A helper method which runs the given cl script with the given command line arguments
168 *
169 * @param scriptText is the text content of the script
170 * @param fileName is the logical file name of the script (which is used to create the class name of the script)
171 * @param list the command line arguments to pass in
172 */
173 public void run(String scriptText, String fileName, List list) throws CompilationFailedException, IOException {
174 String[] args = new String[list.size()];
175 list.toArray(args);
176 run(scriptText, fileName, args);
177 }
178
179 /***
180 * Runs the given script file name with the given command line arguments
181 *
182 * @param scriptFile the file name of the script to run
183 * @param args the command line arguments to pass in
184 */
185 public void run(final File scriptFile, String[] args) throws CompilationFailedException, IOException {
186 String scriptName = scriptFile.getName();
187 int p = scriptName.lastIndexOf(".");
188 if (p++ >= 0) {
189 if (scriptName.substring(p).equals("java")) {
190 System.err.println("error: cannot compile file with .java extension: " + scriptName);
191 throw new CompilationFailedException(0, null);
192 }
193 }
194
195
196 final Thread thread = Thread.currentThread();
197 ClassLoader currentClassLoader = thread.getContextClassLoader();
198
199 class DoSetContext implements PrivilegedAction {
200 ClassLoader classLoader;
201
202 public DoSetContext(ClassLoader loader) {
203 classLoader = loader;
204 }
205
206 public Object run() {
207 thread.setContextClassLoader(classLoader);
208 return null;
209 }
210 }
211 ;
212
213 AccessController.doPrivileged(new DoSetContext(loader));
214
215
216
217 Class scriptClass;
218 try {
219 scriptClass = (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() {
220 public Object run() throws CompilationFailedException, IOException {
221 return loader.parseClass(scriptFile);
222 }
223 });
224 } catch (PrivilegedActionException pae) {
225 Exception e = pae.getException();
226 if (e instanceof CompilationFailedException) {
227 throw (CompilationFailedException) e;
228 } else if (e instanceof IOException) {
229 throw (IOException) e;
230 } else {
231 throw (RuntimeException) pae.getException();
232 }
233 }
234
235 runMainOrTestOrRunnable(scriptClass, args);
236
237
238 AccessController.doPrivileged(new DoSetContext(currentClassLoader));
239 }
240
241 /***
242 * if (theClass has a main method) {
243 * run the main method
244 * } else if (theClass instanceof GroovyTestCase) {
245 * use the test runner to run it
246 * } else if (theClass implements Runnable) {
247 * if (theClass has a constructor with String[] params)
248 * instanciate theClass with this constructor and run
249 * else if (theClass has a no-args constructor)
250 * instanciate theClass with the no-args constructor and run
251 * }
252 */
253 private void runMainOrTestOrRunnable(Class scriptClass, String[] args) {
254 try {
255
256 scriptClass.getMethod("main", new Class[]{String[].class});
257
258 InvokerHelper.invokeMethod(scriptClass, "main", new Object[]{args});
259 } catch (NoSuchMethodException e) {
260
261
262 if (isUnitTestCase(scriptClass)) {
263 runTest(scriptClass);
264 }
265
266
267 else if (Runnable.class.isAssignableFrom(scriptClass)) {
268 Constructor constructor = null;
269 Runnable runnable = null;
270 Throwable reason = null;
271 try {
272
273 constructor = scriptClass.getConstructor(new Class[]{(new String[]{}).getClass()});
274 try {
275
276 runnable = (Runnable) constructor.newInstance(new Object[]{args});
277 } catch (Throwable t) {
278 reason = t;
279 }
280 } catch (NoSuchMethodException e1) {
281 try {
282
283 constructor = scriptClass.getConstructor(new Class[]{});
284 try {
285
286 runnable = (Runnable) constructor.newInstance(new Object[]{});
287 } catch (Throwable t) {
288 reason = t;
289 }
290 } catch (NoSuchMethodException nsme) {
291 reason = nsme;
292 }
293 }
294 if (constructor != null && runnable != null) {
295 runnable.run();
296 } else {
297 throw new GroovyRuntimeException("This script or class could not be run. ", reason);
298 }
299 } else {
300 throw new GroovyRuntimeException("This script or class could not be run. \n" +
301 "It should either: \n" +
302 "- have a main method, \n" +
303 "- be a class extending GroovyTestCase, \n" +
304 "- or implement the Runnable interface.");
305 }
306 }
307 }
308
309 /***
310 * Run the specified class extending GroovyTestCase as a unit test.
311 * This is done through reflection, to avoid adding a dependency to the JUnit framework.
312 * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile
313 * groovy scripts and classes would have to add another dependency on their classpath.
314 *
315 * @param scriptClass the class to be run as a unit test
316 */
317 private void runTest(Class scriptClass) {
318 try {
319 InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object[]{scriptClass});
320 } catch (Exception e) {
321 throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.");
322 }
323 }
324
325 /***
326 * Utility method to check through reflection if the parsed class extends GroovyTestCase.
327 *
328 * @param scriptClass the class we want to know if it extends GroovyTestCase
329 * @return true if the class extends groovy.util.GroovyTestCase
330 */
331 private boolean isUnitTestCase(Class scriptClass) {
332
333
334 boolean isUnitTestCase = false;
335 try {
336 try {
337 Class testCaseClass = this.loader.loadClass("groovy.util.GroovyTestCase");
338
339 if (testCaseClass.isAssignableFrom(scriptClass)) {
340 isUnitTestCase = true;
341 }
342 } catch (ClassNotFoundException e) {
343
344 }
345 } catch (Throwable e) {
346
347 }
348 return isUnitTestCase;
349 }
350
351 /***
352 * Runs the given script text with command line arguments
353 *
354 * @param scriptText is the text content of the script
355 * @param fileName is the logical file name of the script (which is used to create the class name of the script)
356 * @param args the command line arguments to pass in
357 */
358 public void run(String scriptText, String fileName, String[] args) throws CompilationFailedException, IOException {
359 run(new ByteArrayInputStream(scriptText.getBytes()), fileName, args);
360 }
361
362 /***
363 * Runs the given script with command line arguments
364 *
365 * @param in the stream reading the script
366 * @param fileName is the logical file name of the script (which is used to create the class name of the script)
367 * @param args the command line arguments to pass in
368 */
369 public Object run(final InputStream in, final String fileName, String[] args) throws CompilationFailedException, IOException {
370 GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
371 public Object run() {
372 return new GroovyCodeSource(in, fileName, "/groovy/shell");
373 }
374 });
375 Class scriptClass = parseClass(gcs);
376 runMainOrTestOrRunnable(scriptClass, args);
377 return null;
378 }
379
380 public Object getVariable(String name) {
381 return context.getVariables().get(name);
382 }
383
384 public void setVariable(String name, Object value) {
385 context.setVariable(name, value);
386 }
387
388 /***
389 * Evaluates some script against the current Binding and returns the result
390 *
391 * @param codeSource
392 * @return
393 * @throws CompilationFailedException
394 * @throws IOException
395 */
396 public Object evaluate(GroovyCodeSource codeSource) throws CompilationFailedException, IOException {
397 Script script = parse(codeSource);
398 return script.run();
399 }
400
401 /***
402 * Evaluates some script against the current Binding and returns the result
403 *
404 * @param scriptText the text of the script
405 * @param fileName is the logical file name of the script (which is used to create the class name of the script)
406 */
407 public Object evaluate(String scriptText, String fileName) throws CompilationFailedException, ClassNotFoundException, IOException {
408 return evaluate(new ByteArrayInputStream(scriptText.getBytes()), fileName);
409 }
410
411 /***
412 * Evaluates some script against the current Binding and returns the result.
413 * The .class file created from the script is given the supplied codeBase
414 */
415 public Object evaluate(String scriptText, String fileName, String codeBase) throws CompilationFailedException, IOException {
416 return evaluate(new GroovyCodeSource(new ByteArrayInputStream(scriptText.getBytes()), fileName, codeBase));
417 }
418
419 /***
420 * Evaluates some script against the current Binding and returns the result
421 *
422 * @param file is the file of the script (which is used to create the class name of the script)
423 */
424 public Object evaluate(File file) throws CompilationFailedException, IOException {
425 return evaluate(new GroovyCodeSource(file));
426 }
427
428 /***
429 * Evaluates some script against the current Binding and returns the result
430 *
431 * @param scriptText the text of the script
432 */
433 public Object evaluate(String scriptText) throws CompilationFailedException, IOException {
434 return evaluate(new ByteArrayInputStream(scriptText.getBytes()), generateScriptName());
435 }
436
437 /***
438 * Evaluates some script against the current Binding and returns the result
439 *
440 * @param in the stream reading the script
441 */
442 public Object evaluate(InputStream in) throws CompilationFailedException, IOException {
443 return evaluate(in, generateScriptName());
444 }
445
446 /***
447 * Evaluates some script against the current Binding and returns the result
448 *
449 * @param in the stream reading the script
450 * @param fileName is the logical file name of the script (which is used to create the class name of the script)
451 */
452 public Object evaluate(InputStream in, String fileName) throws CompilationFailedException, IOException {
453 Script script = null;
454 try {
455 script = parse(in, fileName);
456 return script.run();
457 } finally {
458 if (script != null) {
459 InvokerHelper.removeClass(script.getClass());
460 }
461 }
462 }
463
464 /***
465 * Parses the given script and returns it ready to be run
466 *
467 * @param in the stream reading the script
468 * @param fileName is the logical file name of the script (which is used to create the class name of the script)
469 * @return the parsed script which is ready to be run via @link Script.run()
470 */
471 public Script parse(final InputStream in, final String fileName) throws CompilationFailedException, IOException {
472 GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
473 public Object run() {
474 return new GroovyCodeSource(in, fileName, "/groovy/shell");
475 }
476 });
477 return parse(gcs);
478 }
479
480 /***
481 * Parses the groovy code contained in codeSource and returns a java class.
482 */
483 private Class parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException, IOException {
484
485 return loader.parseClass(codeSource, false);
486 }
487
488 /***
489 * Parses the given script and returns it ready to be run. When running in a secure environment
490 * (-Djava.security.manager) codeSource.getCodeSource() determines what policy grants should be
491 * given to the script.
492 *
493 * @param codeSource
494 * @return
495 */
496 public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException, IOException {
497 return InvokerHelper.createScript(parseClass(codeSource), context);
498 }
499
500 /***
501 * Parses the given script and returns it ready to be run
502 *
503 * @param file is the file of the script (which is used to create the class name of the script)
504 */
505 public Script parse(File file) throws CompilationFailedException, IOException {
506 return parse(new GroovyCodeSource(file));
507 }
508
509 /***
510 * Parses the given script and returns it ready to be run
511 *
512 * @param scriptText the text of the script
513 */
514 public Script parse(String scriptText) throws CompilationFailedException, IOException {
515 return parse(new ByteArrayInputStream(scriptText.getBytes()), generateScriptName());
516 }
517
518 public Script parse(String scriptText, String fileName) throws CompilationFailedException, IOException {
519 return parse(new ByteArrayInputStream(scriptText.getBytes()), fileName);
520 }
521
522 /***
523 * Parses the given script and returns it ready to be run
524 *
525 * @param in the stream reading the script
526 */
527 public Script parse(InputStream in) throws CompilationFailedException, IOException {
528 return parse(in, generateScriptName());
529 }
530
531 protected synchronized String generateScriptName() {
532 return "Script" + (++counter) + ".groovy";
533 }
534 }