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