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