KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > groovy > lang > GroovyShell


1 /*
2  $Id: GroovyShell.java,v 1.44 2005/06/10 09:55:28 cstein Exp $
3
4  Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5
6  Redistribution and use of this software and associated documentation
7  ("Software"), with or without modification, are permitted provided
8  that the following conditions are met:
9
10  1. Redistributions of source code must retain copyright
11     statements and notices. Redistributions must also contain a
12     copy of this document.
13
14  2. Redistributions in binary form must reproduce the
15     above copyright notice, this list of conditions and the
16     following disclaimer in the documentation and/or other
17     materials provided with the distribution.
18
19  3. The name "groovy" must not be used to endorse or promote
20     products derived from this Software without prior written
21     permission of The Codehaus. For written permission,
22     please contact info@codehaus.org.
23
24  4. Products derived from this Software may not be called "groovy"
25     nor may "groovy" appear in their names without prior written
26     permission of The Codehaus. "groovy" is a registered
27     trademark of The Codehaus.
28
29  5. Due credit should be given to The Codehaus -
30     http://groovy.codehaus.org/
31
32  THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33  ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
36  THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43  OF THE POSSIBILITY OF SUCH DAMAGE.
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 JavaDoc;
56 import java.io.File JavaDoc;
57 import java.io.IOException JavaDoc;
58 import java.io.InputStream JavaDoc;
59 import java.lang.reflect.Constructor JavaDoc;
60 import java.security.AccessController JavaDoc;
61 import java.security.PrivilegedAction JavaDoc;
62 import java.security.PrivilegedActionException JavaDoc;
63 import java.security.PrivilegedExceptionAction JavaDoc;
64 import java.util.HashMap JavaDoc;
65 import java.util.List JavaDoc;
66 import java.util.Map JavaDoc;
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.44 $
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 JavaDoc defineClass(ClassNode classNode, String JavaDoc file, String JavaDoc newCodeBase) {
82             Class JavaDoc c = super.defineClass(classNode,file,newCodeBase);
83             classMap.put(c.getName(),this);
84             return c;
85         }
86     }
87
88     private static ClassLoader JavaDoc getLoader(ClassLoader JavaDoc 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 JavaDoc {
98         public MainClassLoader(ClassLoader JavaDoc parent) {
99             super(getLoader(parent));
100         }
101         protected synchronized Class JavaDoc loadClass(String JavaDoc name, boolean resolve) throws ClassNotFoundException JavaDoc {
102             Object JavaDoc cached = classMap.get(name);
103             if (cached!=null) return (Class JavaDoc) cached;
104             ClassLoader JavaDoc 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 JavaDoc[] EMPTY_ARGS = {};
112
113     
114     private HashMap JavaDoc classMap = new HashMap JavaDoc();
115     private MainClassLoader loader;
116     private Binding context;
117     private int counter;
118     private CompilerConfiguration config;
119
120     public static void main(String JavaDoc[] 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 JavaDoc parent, Binding binding) {
141         this(parent, binding, null);
142     }
143
144     public GroovyShell(ClassLoader JavaDoc parent) {
145         this(parent, new Binding(), null);
146     }
147     
148     public GroovyShell(final ClassLoader JavaDoc 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 JavaDoc 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 JavaDoc getProperty(String JavaDoc property) {
178         Object JavaDoc answer = getVariable(property);
179         if (answer == null) {
180             answer = super.getProperty(property);
181         }
182         return answer;
183     }
184
185     public void setProperty(String JavaDoc property, Object JavaDoc newValue) {
186         setVariable(property, newValue);
187         try {
188             super.setProperty(property, newValue);
189         } catch (GroovyRuntimeException e) {
190             // ignore, was probably a dynamic property
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 JavaDoc scriptFile, List JavaDoc list) throws CompilationFailedException, IOException JavaDoc {
201         String JavaDoc[] args = new String JavaDoc[list.size()];
202         run(scriptFile, (String JavaDoc[]) 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 JavaDoc scriptText, String JavaDoc fileName, List JavaDoc list) throws CompilationFailedException {
213         String JavaDoc[] args = new String JavaDoc[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 JavaDoc scriptFile, String JavaDoc[] args) throws CompilationFailedException, IOException JavaDoc {
225         String JavaDoc 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         // Get the current context classloader and save it on the stack
235
final Thread JavaDoc thread = Thread.currentThread();
236         //ClassLoader currentClassLoader = thread.getContextClassLoader();
237

238         class DoSetContext implements PrivilegedAction JavaDoc {
239             ClassLoader JavaDoc classLoader;
240
241             public DoSetContext(ClassLoader JavaDoc loader) {
242                 classLoader = loader;
243             }
244
245             public Object JavaDoc run() {
246                 thread.setContextClassLoader(classLoader);
247                 return null;
248             }
249         }
250
251         AccessController.doPrivileged(new DoSetContext(loader));
252
253         // Parse the script, generate the class, and invoke the main method. This is a little looser than
254
// if you are compiling the script because the JVM isn't executing the main method.
255
Class JavaDoc scriptClass;
256         final ShellLoader loader = new ShellLoader();
257         try {
258             scriptClass = (Class JavaDoc) AccessController.doPrivileged(new PrivilegedExceptionAction JavaDoc() {
259                 public Object JavaDoc run() throws CompilationFailedException, IOException JavaDoc {
260                     return loader.parseClass(scriptFile);
261                 }
262             });
263         } catch (PrivilegedActionException JavaDoc pae) {
264             Exception JavaDoc e = pae.getException();
265             if (e instanceof CompilationFailedException) {
266                 throw (CompilationFailedException) e;
267             } else if (e instanceof IOException JavaDoc) {
268                 throw (IOException JavaDoc) e;
269             } else {
270                 throw (RuntimeException JavaDoc) pae.getException();
271             }
272         }
273
274         runMainOrTestOrRunnable(scriptClass, args);
275
276         // Set the context classloader back to what it was.
277
//AccessController.doPrivileged(new DoSetContext(currentClassLoader));
278
}
279
280     /**
281      * if (theClass has a main method) {
282      * run the main method
283      * } else if (theClass instanceof GroovyTestCase) {
284      * use the test runner to run it
285      * } else if (theClass implements Runnable) {
286      * if (theClass has a constructor with String[] params)
287      * instanciate theClass with this constructor and run
288      * else if (theClass has a no-args constructor)
289      * instanciate theClass with the no-args constructor and run
290      * }
291      */

292     private void runMainOrTestOrRunnable(Class JavaDoc scriptClass, String JavaDoc[] args) {
293         if (scriptClass == null) {
294             return;
295         }
296         try {
297             // let's find a main method
298
scriptClass.getMethod("main", new Class JavaDoc[]{String JavaDoc[].class});
299         } catch (NoSuchMethodException JavaDoc e) {
300             // As no main() method was found, let's see if it's a unit test
301
// if it's a unit test extending GroovyTestCase, run it with JUnit's TextRunner
302
if (isUnitTestCase(scriptClass)) {
303                 runTest(scriptClass);
304             }
305             // no main() method, not a unit test,
306
// if it implements Runnable, try to instanciate it
307
else if (Runnable JavaDoc.class.isAssignableFrom(scriptClass)) {
308                 Constructor JavaDoc constructor = null;
309                 Runnable JavaDoc runnable = null;
310                 Throwable JavaDoc reason = null;
311                 try {
312                     // first, fetch the constructor taking String[] as parameter
313
constructor = scriptClass.getConstructor(new Class JavaDoc[]{(new String JavaDoc[]{}).getClass()});
314                     try {
315                         // instanciate a runnable and run it
316
runnable = (Runnable JavaDoc) constructor.newInstance(new Object JavaDoc[]{args});
317                     } catch (Throwable JavaDoc t) {
318                         reason = t;
319                     }
320                 } catch (NoSuchMethodException JavaDoc e1) {
321                     try {
322                         // otherwise, find the default constructor
323
constructor = scriptClass.getConstructor(new Class JavaDoc[]{});
324                         try {
325                             // instanciate a runnable and run it
326
runnable = (Runnable JavaDoc) constructor.newInstance(new Object JavaDoc[]{});
327                         } catch (Throwable JavaDoc t) {
328                             reason = t;
329                         }
330                     } catch (NoSuchMethodException JavaDoc nsme) {
331                         reason = nsme;
332                     }
333                 }
334                 if (constructor != null && runnable != null) {
335                     runnable.run();
336                 } else {
337                     throw new GroovyRuntimeException("This script or class could not be run. ", reason);
338                 }
339             } else {
340                 throw new GroovyRuntimeException("This script or class could not be run. \n" +
341                         "It should either: \n" +
342                         "- have a main method, \n" +
343                         "- be a class extending GroovyTestCase, \n" +
344                         "- or implement the Runnable interface.");
345             }
346             return;
347         }
348         // if that main method exist, invoke it
349
InvokerHelper.invokeMethod(scriptClass, "main", new Object JavaDoc[]{args});
350     }
351
352     /**
353      * Run the specified class extending GroovyTestCase as a unit test.
354      * This is done through reflection, to avoid adding a dependency to the JUnit framework.
355      * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile
356      * groovy scripts and classes would have to add another dependency on their classpath.
357      *
358      * @param scriptClass the class to be run as a unit test
359      */

360     private void runTest(Class JavaDoc scriptClass) {
361         try {
362             InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object JavaDoc[]{scriptClass});
363         } catch (Exception JavaDoc e) {
364             throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.");
365         }
366     }
367
368     /**
369      * Utility method to check through reflection if the parsed class extends GroovyTestCase.
370      *
371      * @param scriptClass the class we want to know if it extends GroovyTestCase
372      * @return true if the class extends groovy.util.GroovyTestCase
373      */

374     private boolean isUnitTestCase(Class JavaDoc scriptClass) {
375         // check if the parsed class is a GroovyTestCase,
376
// so that it is possible to run it as a JUnit test
377
final ShellLoader loader = new ShellLoader();
378         boolean isUnitTestCase = false;
379         try {
380             try {
381                 Class JavaDoc testCaseClass = this.loader.loadClass("groovy.util.GroovyTestCase");
382                 // if scriptClass extends testCaseClass
383
if (testCaseClass.isAssignableFrom(scriptClass)) {
384                     isUnitTestCase = true;
385                 }
386             } catch (ClassNotFoundException JavaDoc e) {
387                 // fall through
388
}
389         } catch (Throwable JavaDoc e) {
390             // fall through
391
}
392         return isUnitTestCase;
393     }
394
395     /**
396      * Runs the given script text with command line arguments
397      *
398      * @param scriptText is the text content of the script
399      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
400      * @param args the command line arguments to pass in
401      */

402     public void run(String JavaDoc scriptText, String JavaDoc fileName, String JavaDoc[] args) throws CompilationFailedException {
403         run(new ByteArrayInputStream JavaDoc(scriptText.getBytes()), fileName, args);
404     }
405
406     /**
407      * Runs the given script with command line arguments
408      *
409      * @param in the stream reading the script
410      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
411      * @param args the command line arguments to pass in
412      */

413     public Object JavaDoc run(final InputStream JavaDoc in, final String JavaDoc fileName, String JavaDoc[] args) throws CompilationFailedException {
414         GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction JavaDoc() {
415             public Object JavaDoc run() {
416                 return new GroovyCodeSource(in, fileName, "/groovy/shell");
417             }
418         });
419         Class JavaDoc scriptClass = parseClass(gcs);
420         runMainOrTestOrRunnable(scriptClass, args);
421         return null;
422     }
423
424     public Object JavaDoc getVariable(String JavaDoc name) {
425         return context.getVariables().get(name);
426     }
427
428     public void setVariable(String JavaDoc name, Object JavaDoc value) {
429         context.setVariable(name, value);
430     }
431
432     /**
433      * Evaluates some script against the current Binding and returns the result
434      *
435      * @param codeSource
436      * @return
437      * @throws CompilationFailedException
438      * @throws IOException
439      */

440     public Object JavaDoc evaluate(GroovyCodeSource codeSource) throws CompilationFailedException {
441         Script script = parse(codeSource);
442         return script.run();
443     }
444
445     /**
446      * Evaluates some script against the current Binding and returns the result
447      *
448      * @param scriptText the text of the script
449      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
450      */

451     public Object JavaDoc evaluate(String JavaDoc scriptText, String JavaDoc fileName) throws CompilationFailedException {
452         return evaluate(new ByteArrayInputStream JavaDoc(scriptText.getBytes()), fileName);
453     }
454
455     /**
456      * Evaluates some script against the current Binding and returns the result.
457      * The .class file created from the script is given the supplied codeBase
458      */

459     public Object JavaDoc evaluate(String JavaDoc scriptText, String JavaDoc fileName, String JavaDoc codeBase) throws CompilationFailedException {
460         return evaluate(new GroovyCodeSource(new ByteArrayInputStream JavaDoc(scriptText.getBytes()), fileName, codeBase));
461     }
462
463     /**
464      * Evaluates some script against the current Binding and returns the result
465      *
466      * @param file is the file of the script (which is used to create the class name of the script)
467      */

468     public Object JavaDoc evaluate(File JavaDoc file) throws CompilationFailedException, IOException JavaDoc {
469         return evaluate(new GroovyCodeSource(file));
470     }
471
472     /**
473      * Evaluates some script against the current Binding and returns the result
474      *
475      * @param scriptText the text of the script
476      */

477     public Object JavaDoc evaluate(String JavaDoc scriptText) throws CompilationFailedException {
478         return evaluate(new ByteArrayInputStream JavaDoc(scriptText.getBytes()), generateScriptName());
479     }
480
481     /**
482      * Evaluates some script against the current Binding and returns the result
483      *
484      * @param in the stream reading the script
485      */

486     public Object JavaDoc evaluate(InputStream JavaDoc in) throws CompilationFailedException {
487         return evaluate(in, generateScriptName());
488     }
489
490     /**
491      * Evaluates some script against the current Binding and returns the result
492      *
493      * @param in the stream reading the script
494      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
495      */

496     public Object JavaDoc evaluate(InputStream JavaDoc in, String JavaDoc fileName) throws CompilationFailedException {
497         Script script = null;
498         try {
499             script = parse(in, fileName);
500             return script.run();
501         } finally {
502             if (script != null) {
503                 InvokerHelper.removeClass(script.getClass());
504             }
505         }
506     }
507
508     /**
509      * Parses the given script and returns it ready to be run
510      *
511      * @param in the stream reading the script
512      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
513      * @return the parsed script which is ready to be run via @link Script.run()
514      */

515     public Script parse(final InputStream JavaDoc in, final String JavaDoc fileName) throws CompilationFailedException {
516         GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction JavaDoc() {
517             public Object JavaDoc run() {
518                 return new GroovyCodeSource(in, fileName, "/groovy/shell");
519             }
520         });
521         return parse(gcs);
522     }
523
524     /**
525      * Parses the groovy code contained in codeSource and returns a java class.
526      */

527     private Class JavaDoc parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException {
528         // Don't cache scripts
529
ShellLoader loader = new ShellLoader();
530         return loader.parseClass(codeSource, false);
531     }
532
533     /**
534      * Parses the given script and returns it ready to be run. When running in a secure environment
535      * (-Djava.security.manager) codeSource.getCodeSource() determines what policy grants should be
536      * given to the script.
537      *
538      * @param codeSource
539      * @return
540      */

541     public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException {
542         return InvokerHelper.createScript(parseClass(codeSource), context);
543     }
544
545     /**
546      * Parses the given script and returns it ready to be run
547      *
548      * @param file is the file of the script (which is used to create the class name of the script)
549      */

550     public Script parse(File JavaDoc file) throws CompilationFailedException, IOException JavaDoc {
551         return parse(new GroovyCodeSource(file));
552     }
553
554     /**
555      * Parses the given script and returns it ready to be run
556      *
557      * @param scriptText the text of the script
558      */

559     public Script parse(String JavaDoc scriptText) throws CompilationFailedException {
560         return parse(new ByteArrayInputStream JavaDoc(scriptText.getBytes()), generateScriptName());
561     }
562
563     public Script parse(String JavaDoc scriptText, String JavaDoc fileName) throws CompilationFailedException {
564         return parse(new ByteArrayInputStream JavaDoc(scriptText.getBytes()), fileName);
565     }
566
567     /**
568      * Parses the given script and returns it ready to be run
569      *
570      * @param in the stream reading the script
571      */

572     public Script parse(InputStream JavaDoc in) throws CompilationFailedException {
573         return parse(in, generateScriptName());
574     }
575
576     protected synchronized String JavaDoc generateScriptName() {
577         return "Script" + (++counter) + ".groovy";
578     }
579 }
580
Popular Tags