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