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