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