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